From bc6de8394bcb5db38df7f8d2fd1cf2ec4f923ea0 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 5 Oct 2026 14:17:56 +0000 Subject: [PATCH] Half-odd powers of two conjugate tangent sums are integrated as the exponential they make (a + i a tan(z))^(7/2) (A + B tan(z))/(c - i c tan(z))^(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 sum 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, secant, cosine or sine of z. SolveAConjugatePairOfImaginaryTangentSums integrates in w, after the rule for one sum beside the secant, and returns the integrand times the antiderivative in w over what that differentiates back to. The antiderivative in w is checked there, where it is the only thing computed. 23 of the 24 such rows of 4.3 answered, where master answers 2 within 20 seconds. Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 17 +++ .../Integration/IndefiniteIntegralSolver.cs | 101 ++++++++++++++++++ .../Integration/Integration.Definition.cs | 2 + ...ePairOfImaginaryTangentSumsIntegralTest.cs | 53 +++++++++ 4 files changed, 173 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/ConjugatePairOfImaginaryTangentSumsIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dc9e0db1c..8a86b253f 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -930,6 +930,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 d0203c5c5..1247f40c6 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -26687,6 +26687,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}"); + } + } +}