diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 062394597..87961970a 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -975,6 +975,23 @@ the variable: `(a + i a tan(z))/(q - i q tan(z))^(3/2)`, from 4.3.2.1, ran past | `"cot(c + d*x)^2*(k + q*tan(c + d*x))/(a + i*a*tan(c + d*x))^4".ToEntity().Integrate("x")` | `integral(...)` | powers and logarithms of `a + i a tan(c + d x)`, of `a - i a tan(c + d x)` and of `tan(c + d x)` | | `"(a + i*a*tan(c + d*x))/(q - i*q*tan(c + d*x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | `-2 i a (q - i q tan(c + d x))^(-3/2)/(3d)`, written longer | +### Half-odd powers of two conjugate tangent sums are integrated as the exponential they make + +**Answers in time where they were not.** `(a + i a tan(e + f x))^(7/2) (A + B tan(e + f x))/(c - i c tan(e + f x))^(9/2)` +ran past the budget, with the rest of Rubi's 4.3.2.1 and 4.3.3.1 that put half-odd powers on both sums: the +rule for one of the sums integrates in it beside a whole power of the other, and with both half-odd neither +is. `a + i a tan(z)` is `a sec(z) e^(i z)` on the real line and `c - i c tan(z)` is `c sec(z) e^(-i z)`, so +two powers of them adding up to a whole number `k` are a constant on every interval where they are +continuous times `sec(z)^k e^(i (p - q) z)`, rational in `w = e^(i z)` beside any function of the tangent, +the secant, the cosine or the sine of `z`. They are integrated in `w` now, and the answer is the integrand +times the antiderivative in `w` over what that differentiates back to +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(a+i*a*tan(pe+f*x))^(7/2)*(A+B*tan(pe+f*x))/(c-i*c*tan(pe+f*x))^(9/2)".ToEntity().Integrate("x")` | `integral(...)`; past 20 s on the unreleased master | in a tenth of a second | +| `"1/((a+i*a*tan(pe+f*x))^(7/2)*(c-i*c*tan(pe+f*x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)`; past 20 s on the unreleased master | the same | + ### A symbolic power of one of two conjugate tangent sums is integrated in that sum **Answers where there were none.** `(a + i a tan(c + d x))^m (q - i q tan(c + d x))^4` and the rest diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index f9acaedcb..31ec42351 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -26706,6 +26706,107 @@ private static bool TryReadAnImaginaryTangent(Entity sum, Entity.Variable x, out return true; } + /// + /// Powers of the two conjugate sums a + i a tan(z) and c - i c tan(z), not both + /// whole and adding up to a whole number k, beside a function of the tangent, the + /// secant or the cosine of the same argument, integrated as the exponential they are: + /// a + i a tan(z) is a sec(z) e^(i z) on the real line and + /// c - i c tan(z) is c sec(z) e^(-i z), so the pair is a constant on every + /// interval where it is continuous times sec(z)^k e^(i (p - q) z), and in + /// w = e^(i z) the whole is a rational function of w times a power of it. + /// + /// + /// + /// (a + i a tan(x))^(7/2) (A + B tan(x))/(c - i c tan(x))^(9/2) is + /// e^(8 i x) (A cos(x) + B sin(x)) up to that constant, and ran past the budget with + /// the rest of Rubi's 4.3.2.1 and 4.3.3.1 that put half-odd powers on both sums: the rule for + /// one of them integrates in it beside a whole power of the other, and with both half-odd + /// neither is. + /// + /// + /// The constant is not written, as for one sum beside the secant: the answer is the integrand + /// times the antiderivative in w over what that differentiates back to, a quotient + /// constant wherever it is continuous. The antiderivative in w is checked there, where + /// it is the only thing computed; checked at sampled x, the quotient's constant need + /// not be the same on both sides of the points, and right answers were declined. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + /// + internal static Entity? SolveAConjugatePairOfImaginaryTangentSums(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!expr.Nodes.Any(node => node is Tanf)) + return null; + Entity? argument = null; + Entity? plusPower = null, minusPower = null; + Entity constant = Number.Integer.One; + Entity varying = Number.Integer.One; + Entity rest = Number.Integer.One; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + { + if (!factor.ContainsNode(x)) + { + constant = underneath ? constant / factor : constant * factor; + continue; + } + varying = underneath ? varying / factor : varying * factor; + var (@base, power) = factor is Powf(var b, var p) && !p.ContainsNode(x) + ? (b, p.Evaled is Number.Rational r ? r : p) + : (factor, (Entity)Number.Integer.One); + if (underneath) + power = power is Number.Rational numeric ? -numeric : (-power).InnerSimplified; + if (TryReadAnImaginaryTangent(@base, x, out var tangentOf, out var isPlus)) + { + if (argument is not null && argument != tangentOf || (isPlus ? plusPower : minusPower) is not null) + return null; + argument = tangentOf; + if (isPlus) + plusPower = power; + else + minusPower = power; + continue; + } + rest = underneath ? rest / factor : rest * factor; + } + if (plusPower is null || minusPower is null || argument is null + || plusPower is Number.Integer && minusPower is Number.Integer + || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope)) + return null; + var sum = (plusPower + minusPower).InnerSimplified; + if (sum is not Number && sum.Complexity <= 40) + sum = sum.Simplify(); + if (sum is not Number.Integer { EInteger: var whole } || !whole.CanFitInInt32()) + return null; + var k = whole.ToInt32Checked(); + var phase = (plusPower - minusPower).InnerSimplified; + if (phase is not Number && phase.Complexity <= 40) + phase = phase.Simplify(); + // The rest in w = e^(i z): a function of the tangent, the secant, the cosine and the sine + // of the argument alone, and rational in w once they are written so. + var w = Variable.CreateUnique(expr, "w_exp"); + var secantInW = 2 * w / (MathS.Sqr(w) + 1); + var tangentInW = -MathS.i * (MathS.Sqr(w) - 1) / (MathS.Sqr(w) + 1); + var restInW = rest.Replace(node => node switch + { + Tanf(var inner) when inner == argument => tangentInW, + Secantf(var inner) when inner == argument => secantInW, + Cosf(var inner) when inner == argument => 1 / secantInW, + Sinf(var inner) when inner == argument => tangentInW / secantInW, + _ => node, + }); + if (restInW.ContainsNode(x)) + return null; + // sec(z)^k e^(i p z) R dz is (2 w/(w^2 + 1))^k w^(p - 1) R(w) dw/i, with dz = dw/(i w). + var inW = Functions.SingleQuotient.Combine( + (k == 0 ? Number.Integer.One : MathS.Pow(secantInW, k)) * MathS.Pow(w, (phase - 1).InnerSimplified) * restInW).InnerSimplified; + if (Integration.ComputeAsAQuestionOfItsOwn(inW, w, integrateByParts) is not { } inWAnswer + || inWAnswer.Nodes.Any(node => node == MathS.NaN) + || !Functions.PartialFractions.HoldsAtSampledPoints(inWAnswer.Differentiate(w), inW, w)) + return null; + var exponential = MathS.Pow(MathS.e, MathS.i * argument); + var differentiatesBackTo = (k == 0 ? Number.Integer.One : MathS.Pow(MathS.Sec(argument), k)) * MathS.Pow(exponential, phase) * rest; + return constant * varying * inWAnswer.Substitute(w, exponential) / (MathS.i * slope * differentiatesBackTo); + } + /// /// A cos(y) + i A sin(y) below the bar, written as the exponential it is: /// A e^(i y), and A cos(y) - i A sin(y) as A e^(-i y). Beside a power diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 05c4b58ba..20c6b4da5 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -675,6 +675,8 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // product and takes it apart, and so answers (c sec)^(5/2)/(a + i a tan)^(5/2) with the // wrong constant wherever the cosine is negative. if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAnImaginaryTangentBesideAPowerOfTheSecant(expr, x, integrateByParts)) is { }) return answer; + // And powers of the two conjugate sums, both not whole, as the exponential they make. + if ((answer = IndefiniteIntegralSolver.SolveAConjugatePairOfImaginaryTangentSums(expr, x, integrateByParts)) is { }) return answer; // A rational function of the tangent beside a power of a + i a tan(z), in that sum. if ((answer = IndefiniteIntegralSolver.SolveInTheImaginarySumOfAConstantAndATangent(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveByWritingAnImaginaryTangentAsAnExponential(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/ConjugatePairOfImaginaryTangentSumsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ConjugatePairOfImaginaryTangentSumsIntegralTest.cs new file mode 100644 index 000000000..3bc968e70 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ConjugatePairOfImaginaryTangentSumsIntegralTest.cs @@ -0,0 +1,53 @@ +// +// 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 +{ + /// + /// Powers of the conjugate sums a + i a tan(x) and c - i c tan(x), both half-odd, + /// beside a function of the tangent: a + i a tan(x) is a sec(x) e^(i x) and + /// c - i c tan(x) is c sec(x) e^(-i x), so the pair is a constant times + /// sec(x)^k e^(i (p - q) x), rational in e^(i x). Rubi's 4.3.2.1 and 4.3.3.1. The + /// integrands are complex for a real x, and compared as complex numbers. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ConjugatePairOfImaginaryTangentSumsIntegralTest + { + [Theory] + [InlineData("(a + i*a*tan(x))^(7/2)*(A + B*tan(x))/(c - i*c*tan(x))^(9/2)")] + [InlineData("(a + i*a*tan(x))^(3/2)*(A + B*tan(x))/(c - i*c*tan(x))^(3/2)")] + [InlineData("(a + i*a*tan(x))^(3/2)/(c - i*c*tan(x))^(3/2)")] + [InlineData("1/((a + i*a*tan(x))^(7/2)*(c - i*c*tan(x))^(3/2))")] + [InlineData("(A + B*tan(x))/((a + i*a*tan(x))^(3/2)*(c - i*c*tan(x))^(3/2))")] + public void AsTheExponentialTheyMake(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("c", 0.7).Substitute("A", 0.4).Substitute("B", 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}"); + } + } +}