From 664ac57979d1045cbe6a5f0ffd971c3015202de0 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Tue, 6 Oct 2026 15:45:08 +0000 Subject: [PATCH] A power of the secant beside a power of a + i a tan is integrated in the sum, whatever the powers Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 18 +++ .../Integration/IndefiniteIntegralSolver.cs | 118 ++++++++++++++++++ .../Integration/Integration.Definition.cs | 2 + ...BesideAnImaginaryTangentSumIntegralTest.cs | 54 ++++++++ 4 files changed, 192 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/SecantBesideAnImaginaryTangentSumIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 67f202fff..8bf45eebb 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -365,6 +365,24 @@ quotient of two such linears the sum is `(b - d t^2)^2 + (c t^2 - a)^2`. Rubi's | `"(c + d*tan(x))^(3/2)/(a + b*tan(x))^3".ToEntity().Integrate("x")` | `integral(...)` | the same | | `"1/((a + b*tan(x))^(3/2)*(c + d*tan(x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)`; past a minute on the unreleased master | in the root of the quotient of the two, in a second | +### A power of the secant beside a power of `a + i a tan` is integrated in the sum, whatever the powers + +**Answers where there were none.** `sec(x)^3 sqrt(a + i a tan(x))` was declined, with the rest of +Rubi's 4.3.1.2 whose powers add up to no whole number. Under `u = a + i a tan(z)`, +`du = i a sec(z)^2 dz` and `sec(z)^2 = (u/a) ((2 a - u)/a)`, so a power of the secant, or of the +cosine, beside a power of the sum is a power of `u` beside a power of `2 a - u`. The antiderivative in +`u` is found for a real `u`, and its conditions -- a radicand at least zero -- hold nowhere on the +path, where `u` is not real; they are dropped, and the answer is kept only where its derivative is the +integrand at sampled points. `cos(x)^9 (a + i a tan(x))^(7/2)` was answered on the unreleased master +with the condition `1 + i tan(x) >= 0`, which no real `x` but the zeros of the tangent meets; it is +answered without one now ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sec(x)^3*sqrt(a + i*a*tan(x))".ToEntity().Integrate("x")` | `integral(...)` | powers of `sqrt(a - i a tan(x))` times a factor constant wherever it is continuous, 495 characters | +| `"sec(x)^5/(a + i*a*tan(x))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | the same, 399 characters | +| `"cos(x)^9*(a + i*a*tan(x))^(7/2)".ToEntity().Integrate("x")` | `integral(...)`; on the unreleased master, an answer provided `1 + i tan(x) >= 0` | 1,067 characters, unconditional | + ### A quotient in `x^2` over a power of a linear in `x^2` and a biquadratic is split in `x^2` **Shorter answers, sooner.** `sqrt(c + d tan(x)) (A + B tan(x) + C tan(x)^2)/(a + b tan(x))^3` was diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 729347128..fa6842798 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -27007,6 +27007,124 @@ static Entity SumOfThePowers(Entity first, Entity second) => (first + second).InnerSimplified is var sum && (sum is Number || sum.Complexity > 40) ? sum : sum.Simplify(); } + /// + /// A power of the secant, or of the cosine, of z beside a power of A + i A tan(z), + /// whatever the powers, integrated in that sum: under u = A + i A tan(z), + /// du = i A sec(z)^2 dz and sec(z)^2 = (u/A) ((2 A - u)/A), so + /// sec(z)^s u^n dz is u^n ((u/A) ((2 A - u)/A))^r du/(i A) with + /// r = (s - 2)/2, up to a factor constant wherever it is continuous; and + /// (u/A)^r ((2 A - u)/A)^r is (sec(z)^2)^r exactly, the two being + /// 1 + i tan(z) and 1 - i tan(z), whose arguments are opposite and less than a + /// right angle. + /// + /// + /// + /// sec(x)^3 sqrt(a + i a tan(x)) was declined, with the rest of Rubi's 4.3.1.2 whose + /// powers do not add up to a whole number, which + /// integrates as an + /// exponential and this does not: sec^5/(a + i a tan)^(3/2), + /// (e cos)^(3/2) sqrt(a + i a tan). In u each is a power of u beside a + /// power of 2 A - u. + /// + /// + /// The constant is not written, as there: the answer is the integrand times the + /// antiderivative in u over what that antiderivative differentiates back to, a + /// quotient constant wherever it is continuous. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + /// + internal static Entity? SolveAPowerOfTheSecantBesideAPowerOfAnImaginaryTangentSumInTheSum(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!expr.Nodes.Any(node => node is Tanf) || !expr.Nodes.Any(node => node is Secantf or Cosf)) + return null; + Entity? argument = null, sum = null; + var plus = false; + Entity? sumPower = null; + Entity secantPower = Number.Integer.Zero; + var sawSecant = false; + foreach (var (factor, underneath) in FactorsOfTheIntegrand(expr)) + { + if (!factor.ContainsNode(x)) + continue; + 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 (sumPower is not null || argument is not null && argument != tangentOf) + return null; + (sumPower, argument, plus, sum) = (power, tangentOf, isPlus, @base); + continue; + } + var (trigonometric, sign) = @base switch + { + Secantf => (@base, 1), + Cosf => (@base, -1), + Mulf(var left, var right) when !left.ContainsNode(x) && right is Secantf => (right, 1), + Mulf(var left, var right) when !left.ContainsNode(x) && right is Cosf => (right, -1), + Mulf(var left, var right) when !right.ContainsNode(x) && left is Secantf => (left, 1), + Mulf(var left, var right) when !right.ContainsNode(x) && left is Cosf => (left, -1), + _ => ((Entity?)null, 0) + }; + var of = trigonometric switch { Secantf(var inner) => inner, Cosf(var inner) => inner, _ => null }; + if (of is null || argument is not null && argument != of) + return null; + argument = of; + secantPower = sign == 1 ? secantPower + power : secantPower - power; + sawSecant = true; + } + if (sumPower is null || sum is null || argument is null || !sawSecant + || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope)) + return null; + secantPower = secantPower is Number ? secantPower : secantPower.InnerSimplified; + // Whole powers on both are a rational function of the sine and the cosine, which the + // rules for those answer in a fraction of a second, where in u they ran past five: + // `cos(x)^5/(a + i a tan(x))^3`. + if (sumPower is Number.Integer && secantPower is Number.Integer) + return null; + Entity constantTerm = Number.Integer.Zero; + foreach (var term in Sumf.LinearChildren(sum)) + if (!term.ContainsNode(x)) + constantTerm += term; + var a = constantTerm.InnerSimplified; + var u = Variable.CreateUnique(expr, "u_tan"); + var half = ((secantPower - 2) / 2).InnerSimplified; + Entity squared = MathS.Pow(u / a, half) * MathS.Pow((2 * a - u) / a, half); + var inU = MathS.Pow(u, sumPower) * squared; + if (Integration.ComputeAsAQuestionOfItsOwn(inU, u, integrateByParts) is not { } inUAnswer + || inUAnswer.Nodes.Any(node => node == MathS.NaN)) + return null; + // du/dx is +-i A z' sec(z)^2. + var derivativeOfU = (plus ? MathS.i : -MathS.i) * a * slope * MathS.Pow(MathS.Sec(argument), 2); + var differentiatesBackTo = inU.Substitute(u, sum) * derivativeOfU; + // The antiderivative in u was found for a real u, and its conditions say so -- a radicand + // at least zero -- where u = A + i A tan(z) is not real: kept, they hold nowhere on the + // path and the answer has no value at all. The formula is an antiderivative wherever it is + // analytic, so the conditions go, each piecewise taken arm by arm, and an answer is kept + // only where its derivative is the integrand at the sampled points. + foreach (var arm in new[] { 0, -1 }) + { + var formula = WithoutConditions(inUAnswer, arm); + var answer = expr * formula.Substitute(u, sum) / differentiatesBackTo; + if (Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x)) + return answer; + if (!inUAnswer.Nodes.Any(node => node is Piecewise)) + break; + } + return null; + + // Every condition dropped, and of every piecewise the first arm, or with -1 the last. + static Entity WithoutConditions(Entity e, int arm) + => e.Replace(node => node switch + { + Providedf(var inner, _) => inner, + Piecewise piecewise when piecewise.Cases.Any() => arm == 0 ? piecewise.Cases.First().Expression : piecewise.Cases.Last().Expression, + _ => node + }); + } + /// /// A + i A tan(z) or A - i A tan(z), with a constant A: the argument, /// and whether the imaginary unit comes with a plus. diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 15073706b..cd915e3f9 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -683,6 +683,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 whatever the powers, in the sum, where they do not add up to a whole number. + if ((answer = IndefiniteIntegralSolver.SolveAPowerOfTheSecantBesideAPowerOfAnImaginaryTangentSumInTheSum(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. diff --git a/Sources/Tests/UnitTests/Calculus/SecantBesideAnImaginaryTangentSumIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/SecantBesideAnImaginaryTangentSumIntegralTest.cs new file mode 100644 index 000000000..2bd431a9b --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/SecantBesideAnImaginaryTangentSumIntegralTest.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 the secant or the cosine beside a power of a + i a tan(x), the powers adding + /// up to no whole number, integrated in that sum: under u = a + i a tan(x), + /// sec(x)^2 = (u/a) ((2 a - u)/a). Rubi's 4.3.1.2. The integrands are complex for a real + /// x and are compared as complex numbers. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class SecantBesideAnImaginaryTangentSumIntegralTest + { + [Theory] + [InlineData("sec(x)^3*sqrt(a + i*a*tan(x))")] + [InlineData("sec(x)^5/(a + i*a*tan(x))^(3/2)")] + [InlineData("(k*cos(x))^(3/2)*sqrt(a + i*a*tan(x))")] + [InlineData("cos(x)^9*(a + i*a*tan(x))^(7/2)")] + [InlineData("(m*sec(x))^(2/3)*(a + i*a*tan(x))^(5/3)")] + public void InTheSum(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("k", 0.7).Substitute("m", 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}"); + } + } +}