diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 7a9dc5dfe..fd7d5df4d 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -177,6 +177,22 @@ the division's; with it on, the rounding is the setting's own and stays | `"(-60.5)!"` at 30 digits, downcasting off | `5.86118478907722232671451280188E-81` | `2.93059239453861116335725639905E-81` | | `"abs(75 + 316.22776601683796i)"`, downcasting off | `325` | `325.000000000000026076735749…` | +### `a + i a tan` beside the tangent's differential is lowered over its conjugate + +**Answers where there were none.** `sqrt(a + i a tan(x))/(c + d tan(x))^(3/2)` ran past thirty seconds: +under `u = tan(x)` it is `sqrt(a + i a u)/((c + d u)^(3/2) (1 + u^2))`, a search, where +`(a + i a u)(a - i a u)` is `a^2 (1 + u^2)` and the sum's power is lowered by one over its conjugate, a root +of a linear over a linear, which is answered in a second +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sqrt(a + i*a*tan(x))/(c + d*tan(x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 1,893 characters | +| `"(a + i*a*tan(x))^(3/2)*(c + d*tan(x))^(5/2)".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 670 characters | +| `"sqrt(a + i*a*tan(x))/sqrt(c + d*tan(x))".ToEntity().Integrate("x")` | `integral(...)` | 169 characters | +| `"1/(sqrt(a + i*a*tan(x))*(c + d*tan(x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 2,217 characters | +| `"1/((a + i*a*tan(x))^(3/2)*(c + d*tan(x))^(5/2))".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 3,333 characters | + ### One linear twice is not two linear powers **Answers that had no value.** `(a c + b c x)^(-3 - 2 p) (f + g x) (a^2 + 2 a b x + b^2 x^2)^p` was answered diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index f632213fd..06b630a3a 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -10360,8 +10360,14 @@ static bool IsTwice(Entity argument, Entity.Variable x) // none: `1/(u^2*(1 + u^2))` is integrated and `(1/u)^2/(1 + u^2)`, which is the same // expression, is not. The half-angle and exponential substitutions comb their // integrands for the same reason. - var integrand = Functions.SingleQuotient - .Combine(inU / (1 + MathS.Sqr(uSub))).Simplify(); + // A power of A + i A u beside the differential: (A + i A u)(A - i A u) is A^2 (1 + u^2), + // so 1/(1 + u^2) is A^2/((A + i A u)(A - i A u)) and the sum's power is lowered by one, + // a root of a linear over a linear. `sqrt(a + i a tan(x))/(c + d tan(x))^(3/2)` was a + // search past thirty seconds as a quotient by 1 + u^2, and in the sum's conjugate it is + // answered in a second. + var integrand = ByTheSumsConjugate(inU, uSub) is { } overTheConjugate + ? overTheConjugate + : Functions.SingleQuotient.Combine(inU / (1 + MathS.Sqr(uSub))).Simplify(); if (integrand is Providedf(var withoutCondition, _)) integrand = withoutCondition; // Asked as the same question: the rewrite renames the variable and adds no step of @@ -10373,6 +10379,34 @@ static bool IsTwice(Entity argument, Entity.Variable x) ? result.Substitute(uSub, tangent) : null; } + + /// + /// inU/(1 + u^2) written with the conjugate of a factor A +- i A u of + /// : inU/(A +- i A u) A^2/(A -+ i A u). Null where no factor is + /// such a sum. + /// + private static Entity? ByTheSumsConjugate(Entity inU, Entity.Variable u) + { + // A reciprocal of a product read as the quotient it is: a constant taken out of the + // sum's root leaves `(sqrt(1 + i u) (c + d u)^(3/2))^(-1)`, whose factors are below the bar. + if (inU is Powf(var reciprocal, Number.Integer { EInteger: var minusOne }) && minusOne.Equals(EInteger.FromInt32(-1))) + inU = 1 / reciprocal; + foreach (var (factor, _) in FactorsOfTheIntegrand(inU)) + { + // A root of the sum, not a whole power: a polynomial in it beside the cosine is the + // rules' for the sine and the cosine, which answer `cos(x)^6 (a + i a tan(x))` sooner. + if (factor is not Powf(var sum, Number.Rational { ERational.Denominator: var denominator }) || denominator.Equals(EInteger.One)) + continue; + if (sum is not Sumf and not Minusf || !TreeAnalyzer.TryGetPolyLinear(sum, u, out var slope, out var intercept) + || TreeAnalyzer.IsZero(intercept) || TreeAnalyzer.IsZero(slope)) + continue; + if (!TreeAnalyzer.IsZero((slope - MathS.i * intercept).Simplify()) && !TreeAnalyzer.IsZero((slope + MathS.i * intercept).Simplify())) + continue; + var conjugate = intercept - slope * u; + return (inU / sum * MathS.Sqr(intercept) / conjugate).InnerSimplified; + } + return null; + } /// /// (prod f_i^(k_i))^(p/q), q odd, as prod f_i^(k_i p/q) where every /// f_i is a trigonometric function of itself or free of it, diff --git a/Sources/Tests/UnitTests/Calculus/ImaginaryTangentSumBesideARootOfALinearInTheTangentIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ImaginaryTangentSumBesideARootOfALinearInTheTangentIntegralTest.cs new file mode 100644 index 000000000..b8012d1ae --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ImaginaryTangentSumBesideARootOfALinearInTheTangentIntegralTest.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using System; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// A power of a + i a tan(x) beside a root of c + d tan(x). Under u = tan(x), + /// dx = du/(1 + u^2) and (a + i a u)(a - i a u) = a^2 (1 + u^2), so the sum's power is + /// lowered by one over its conjugate: a root of a linear over a linear. Rubi's 4.3.2.1. The + /// integrands are complex for a real x and are compared as complex numbers. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ImaginaryTangentSumBesideARootOfALinearInTheTangentIntegralTest + { + [Theory] + [InlineData("sqrt(a + i*a*tan(x))/(c + d*tan(x))^(3/2)")] + [InlineData("(a + i*a*tan(x))^(3/2)*(c + d*tan(x))^(5/2)")] + [InlineData("sqrt(a + i*a*tan(x))/sqrt(c + d*tan(x))")] + [InlineData("1/(sqrt(a + i*a*tan(x))*(c + d*tan(x))^(3/2))")] + [InlineData("1/((a + i*a*tan(x))^(3/2)*(c + d*tan(x))^(5/2))")] + public void OverTheSumsConjugate(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("c", 0.7).Substitute("d", 1.1); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -1.2, -0.7, 0.3, 0.8, 1.3, 2.9 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + } +}