From d4d6c89ef72df2b46642712fe5aa244f3850e6ca Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 20:44:17 +0000 Subject: [PATCH] a + i a tan below the bar is integrated beside a root of the tangent, and beside q - i q tan above it sqrt(tan(c + d x)) (A + B tan(c + d x))/(a + i a tan(c + d x)) ran past the budget, with the rest of Rubi's 4.3.3.1 beside a root of the tangent or the cotangent, and so did (a + i a tan(z))^2 (A + B tan(z))/(q - i q tan(z))^4. Beside a root of the tangent the exponential spelling of the sum is read by nothing, and the integrand is left to t = tan(z). Above the bar a sum is written as its exponential too, and a polynomial in e^(i z) and its reciprocal is multiplied out, each exponent gathered to its slope and offset. Part of #718 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 18 +++++ .../Integration/IndefiniteIntegralSolver.cs | 70 ++++++++++++++++--- ...ryTangentSumAsAnExponentialIntegralTest.cs | 50 +++++++++++++ 3 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/ImaginaryTangentSumAsAnExponentialIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dc39a1fe6..306ffb5b4 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -360,6 +360,24 @@ power up each step, and what reaches the first power is integrated once over the | `"1/(a + b*x^2 + c*x^4)^3".ToEntity().Integrate("x")` | `integral(...)` | the same | | `"x/(a + b*x^3 + c*x^6)^2".ToEntity().Integrate("x")` | `integral(...)` | a polynomial over `a + b x^3 + c x^6`, and the integral of a polynomial over it | +### `a + i a tan(z)` below the bar is integrated beside a root of the tangent, and beside `q - i q tan(z)` above it + +**Answers where there were none.** `sqrt(tan(c + d x)) (A + B tan(c + d x))/(a + i a tan(c + d x))` +ran past the budget, with the rest of Rubi's 4.3.3.1 beside a root of the tangent or the +cotangent, and so did `(a + i a tan(z))^2 (A + B tan(z))/(q - i q tan(z))^4` and the other whole +powers of the two sums on the two sides of the bar. `a + i a tan(z)` below the bar is +`a e^(i z)/cos(z)`, and the integrand was written so wherever the sum was found. Beside a root of +the tangent that spelling is read by nothing, and the integrand is left to the substitution +`t = tan(z)`, which answers it as written. Above the bar, `q - i q tan(z)` was left in sines and +cosines, a sum of exponentials that nothing gathered; it is `q e^(-i z)/cos(z)` the same way now, +and the two together are a polynomial in `e^(i z)` and its reciprocal, multiplied out +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sqrt(tan(c + d*x))*(A + B*tan(c + d*x))/(a + i*a*tan(c + d*x))".ToEntity().Integrate("x")` | `integral(...)` | arctangents and logarithms of `sqrt(tan(c + d x))` | +| `"(a + i*a*tan(c + d*x))^2*(A + B*tan(c + d*x))/(q - i*q*tan(c + d*x))^4".ToEntity().Integrate("x")` | `integral(...)` | a sum of multiples of `e^(k i (c + d x))` | + ### A polynomial over a power of one linear with a symbol in it is written in powers of the linear **Answers where there were none.** `t^9/(a + b t)^8` is a polynomial and eight powers of `1/(a + b t)`, diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 875a31f1c..bbfb00b64 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -23753,7 +23753,7 @@ static bool IsAWholeNumberOfSteps(ERational difference) if (!below.ContainsNode(x)) return null; var arguments = new List(); - var rewritten = below.Replace(node => + Entity AsAnExponential(Entity node) { if (node is not Sumf and not Minusf || !node.ContainsNode(x)) return node; @@ -23807,14 +23807,32 @@ static bool IsAWholeNumberOfSteps(ERational difference) return isTangent ? constant * exponential / MathS.Cos(argument) : (plus ? MathS.i : -MathS.i) * constant * exponential / MathS.Sin(argument); - }); + } + var rewritten = below.Replace(AsAnExponential); if (rewritten == below) return null; - // A tangent or a cotangent of the same argument above the bar in sines and cosines, so that - // the cosine or sine the identity puts below cancels it: `tan(z)/(A + i A tan(z))` is - // `sin(z) e^(-i z)/A`, an exponential times a sine, where `tan(z) cos(z) e^(-i z)/A` was - // a search past the budget with `z = c + d x`. - var inSinesAndCosines = above.Replace(node => node switch + // A root of the tangent or the cotangent of that argument, or of a constant times one, is + // read as written by the substitution `t = tan(z)`, where it is a root of `t`, and by + // nothing in the exponential's spelling: `sqrt(tan(c + d x))/(a + i a tan(c + d x))` + // rewritten was seven seconds of search to be declined, and is a third of one in `t`. + bool ARootOfTheTangent(Entity node) + { + if (node is not Powf(var radicand, Number.Rational power) || power is Number.Integer) + return false; + var varying = Mulf.LinearChildren(radicand).Where(factor => factor.ContainsNode(x)).ToList(); + return varying is [Tanf(var tangentOf)] && arguments.Contains(tangentOf) + || varying is [Cotanf(var cotangentOf)] && arguments.Contains(cotangentOf); + } + if (expr.Nodes.Any(ARootOfTheTangent)) + return null; + // The sums above the bar as exponentials too, now that one below has called for them: + // `(a + i a tan(z))^2/(c - i c tan(z))^4` is `a^2 e^(6 i z) cos(z)^2/c^4`, where the square + // above in sines and cosines was a sum of exponentials that nothing gathered, and a search + // past the budget. And a tangent or a cotangent of the same argument above the bar in sines + // and cosines, so that the cosine or sine the identity puts below cancels it: + // `tan(z)/(A + i A tan(z))` is `sin(z) e^(-i z)/A`, an exponential times a sine, where + // `tan(z) cos(z) e^(-i z)/A` was a search past the budget with `z = c + d x`. + var inSinesAndCosines = above.Replace(AsAnExponential).Replace(node => node switch { Tanf(var inner) when arguments.Contains(inner) => MathS.Sin(inner) / MathS.Cos(inner), Cotanf(var inner) when arguments.Contains(inner) => MathS.Cos(inner) / MathS.Sin(inner), @@ -23840,7 +23858,7 @@ static bool IsAWholeNumberOfSteps(ERational difference) return Integration.ComputeAsTheSameQuestion((above / rewritten).InnerSimplified, x, integrateByParts); // And what sines and cosines of it are left, as the exponentials they are, so that the whole is // a rational function of `e^(i z)`: `tan(z)^2/(A + i A tan(z))` leaves `sin(z)^2/(A cos(z) e^(i z))`. - var inExponentials = (top / bottom).Replace(node => node switch + Entity InExponentials(Entity written) => written.Replace(node => node switch { Sinf(var inner) when arguments.Contains(inner) => (MathS.Pow(MathS.e, MathS.i * inner) - MathS.Pow(MathS.e, -MathS.i * inner)) / (2 * MathS.i), @@ -23848,7 +23866,41 @@ static bool IsAWholeNumberOfSteps(ERational difference) => (MathS.Pow(MathS.e, MathS.i * inner) + MathS.Pow(MathS.e, -MathS.i * inner)) / 2, _ => node, }); - return Integration.ComputeAsTheSameQuestion(inExponentials.InnerSimplified, x, integrateByParts); + // Multiplied out where it is a polynomial in `e^(i z)` and its reciprocal with nothing else of x + // in it, the exponentials below the bar taken above as their reciprocals: a sum of exponentials, + // each term one the closed rule answers. `(a + i a tan(z))^2 (A + B tan(z))/(c - i c tan(z))^6` + // is `a^2 e^(8 i z) cos(z)^4 (A + B tan(z))/c^6`, fourteen seconds through the hyperbolic + // tangent's substitution as written and forty milliseconds multiplied out. Not beside a + // polynomial in x, which would be multiplied out too, and its answer with it. + Entity? Reciprocal(Entity below) => below switch + { + _ when !below.ContainsNode(x) => 1 / below, + Mulf(var left, var right) => Reciprocal(left) is { } ofLeft && Reciprocal(right) is { } ofRight ? ofLeft * ofRight : null, + Powf(var @base, var exponent) when @base == MathS.e => MathS.Pow(MathS.e, -exponent), + Powf(var inner, Number.Integer power) => Reciprocal(inner) is { } ofInner ? MathS.Pow(ofInner, power) : null, + _ => null, + }; + if (Reciprocal(bottom) is not { } belowTakenAbove + || top.Replace(node => node is Sinf or Cosf || TheExponentOf(node) is not null ? Number.Integer.One : node).ContainsNode(x)) + return Integration.ComputeAsTheSameQuestion(InExponentials(top / bottom).InnerSimplified, x, integrateByParts); + var overTheBar = top * belowTakenAbove; + // The exponentials of each term multiplied out gathered into one, whose exponent, a sum of + // theirs, `i x (-1) + i x`, is gathered to its slope and offset, so that a term whose + // exponents cancel is a constant. + var multipliedOut = Patterns.GatherPowersOfOneBase(InExponentials(overTheBar).Expand().InnerSimplified).Replace(node => + TheExponentOf(node) is { } exponent && exponent.ContainsNode(x) + && TreeAnalyzer.TryGetPolyLinear(exponent, x, out var slope, out var offset) + ? MathS.Pow(MathS.e, slope.InnerSimplified * x + offset.InnerSimplified) + : node); + return Integration.ComputeAsTheSameQuestion(multipliedOut.InnerSimplified, x, integrateByParts); + + // The exponent of an exponential, or of a whole power of one, as one exponential. + static Entity? TheExponentOf(Entity node) => node switch + { + Powf(var @base, var exponent) when @base == MathS.e => exponent, + Powf(Powf(var @base, var exponent), Number.Integer times) when @base == MathS.e => exponent * times, + _ => null, + }; } /// diff --git a/Sources/Tests/UnitTests/Calculus/ImaginaryTangentSumAsAnExponentialIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ImaginaryTangentSumAsAnExponentialIntegralTest.cs new file mode 100644 index 000000000..b895e6dc7 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ImaginaryTangentSumAsAnExponentialIntegralTest.cs @@ -0,0 +1,50 @@ +// +// 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 + i a tan(z) below the bar, which is a e^(i z)/cos(z): beside a root of the + /// tangent the integrand is left to the substitution t = tan(z), where the root is a root + /// of t; and beside c - i c tan(z) above the bar, c e^(-i z)/cos(z), the + /// two are a polynomial in e^(i z) and its reciprocal, multiplied out. Rubi's 4.3.3.1. + /// #718 + /// + /// Compared as complex numbers, the integrands being complex. + [Trait("Area", "Calculus")] + public sealed class ImaginaryTangentSumAsAnExponentialIntegralTest + { + [Theory] + [InlineData("sqrt(tan(c + d*x))/(a + i*a*tan(c + d*x))")] + [InlineData("sqrt(tan(c + d*x))*(A + B*tan(c + d*x))/(a + i*a*tan(c + d*x))")] + [InlineData("tan(c + d*x)^(3/2)*(A + B*tan(c + d*x))/(a + i*a*tan(c + d*x))^2")] + [InlineData("(A + B*tan(c + d*x))/(sqrt(cot(c + d*x))*(a + i*a*tan(c + d*x)))")] + [InlineData("(a + i*a*tan(c + d*x))^2*(A + B*tan(c + d*x))/(q - i*q*tan(c + d*x))^4")] + [InlineData("(a + i*a*tan(c + d*x))^3*(A + B*tan(c + d*x))/(q - i*q*tan(c + d*x))^5")] + public void IsIntegrated(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("A", 1.1).Substitute("B", 0.6) + .Substitute("c", 0.4).Substitute("d", 1.1).Substitute("q", 0.7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { -1.1, -0.6, 0.3, 0.9 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + 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}"); + } + } + } +}