From 922f8750d868c4498f696a1d423d4ae3c8dafd86 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 17 Oct 2026 20:18:50 +0000 Subject: [PATCH 1/2] Write an exponential of a symbolic multiple of i arctan as powers of the linears e^(k i arctan(L)) is (1 + i L)^(k/2) (1 - i L)^(-k/2) and (K (1 + L^2))^q is K^q (1 + i L)^q (1 - i L)^q, both exactly for a real L and any k and q. (c + a^2 c x^2)^p/e^(2 i p arctan(a x)) is then c^p (1 - i a x)^(2p), a power of a linear; it was left unevaluated (Rubi's 5.3.6:502 and :503). Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 87 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 2 + ...bolicMultipleOfAnArctangentIntegralTest.cs | 57 ++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/AnExponentialOfASymbolicMultipleOfAnArctangentIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index af073897e..e839d2365 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -9678,6 +9678,93 @@ node is Powf(var @base, Number.Rational power) && power is not Number.Integer && return ratio; } + /// + /// An exponential of a symbolic multiple of i arctan(L), L linear, beside + /// powers of multiples of 1 + L^2, written as powers of the two linears: + /// e^(k i arctan(L)) is (1 + i L)^(k/2) (1 - i L)^(-k/2) and + /// (K (1 + L^2))^q is K^q (1 + i L)^q (1 - i L)^q, each exactly for a real + /// L and any k and q: the two principal arguments are + /// arctan(L) and its negative, and 1 + L^2 is positive. + /// + /// + /// Rubi's 5.3.6 (c + a^2 c x^2)^p/e^(2 i p arctan(a x)) is then + /// c^p (1 - i a x)^(2p), a power of a linear; it was left unevaluated. + /// takes a rational + /// multiple, and this the rest. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveAnExponentialOfASymbolicMultipleOfAnArctangentAsPowersOfTheLinears(Entity expr, Entity.Variable x, bool integrateByParts) + { + static IEnumerable<(Entity Factor, bool Below)> Factors(Entity e, bool below) => e switch + { + Mulf(var l, var r) => Factors(l, below).Concat(Factors(r, below)), + Divf(var l, var r) => Factors(l, below).Concat(Factors(r, !below)), + _ => new[] { (e, below) } + }; + var factors = Factors(expr, false).ToList(); + Entity? argument = null; + Entity? multiple = null; + foreach (var (factor, below) in factors) + { + if (factor is not Powf(var e, var exponent) || e != MathS.e || !exponent.ContainsNode(x)) + continue; + if (argument is not null) + return null; + var arctangents = exponent.Nodes.OfType().Distinct().ToList(); + if (arctangents.Count != 1 || !TreeAnalyzer.TryGetPolyLinear(arctangents[0].Argument, x, out var slope, out _) || TreeAnalyzer.IsZero(slope)) + return null; + var placeholder = Variable.CreateUnique(exponent, "u_atan"); + if (!TreeAnalyzer.TryGetPolyLinear(exponent.Replace(node => node == arctangents[0] ? placeholder : node), placeholder, out var coefficient, out var free) + || coefficient is null || free is null || coefficient.ContainsNode(x) || !TreeAnalyzer.IsZero(free)) + return null; + // Simplified, since `2 i p/i` is not reduced to `2 p` short of it. + var k = (coefficient / MathS.i).Simplify(); + // A rational multiple is the other rule's. + if (k.Evaled is Number.Rational) + return null; + argument = arctangents[0].Argument; + multiple = below ? -k : k; + } + if (argument is null || multiple is null) + return null; + var quadratic = 1 + MathS.Sqr(argument); + Entity constant = Number.Integer.One; + Entity plusPower = multiple / 2, minusPower = -multiple / 2; + foreach (var (factor, below) in factors) + { + if (factor is Powf(var e, var exponent) && e == MathS.e && exponent.ContainsNode(x)) + continue; + if (!factor.ContainsNode(x)) + { + constant = below ? constant / factor : constant * factor; + continue; + } + var (@base, power) = factor is Powf(var b, var q) ? (b, q) : (factor, (Entity)Number.Integer.One); + if (power.ContainsNode(x) + || !TreeAnalyzer.TryGetPolyQuadratic(@base, x, out var a2, out var b2, out var c2) + || !TreeAnalyzer.TryGetPolyQuadratic(quadratic, x, out var a3, out var b3, out var c3) + || a2 is null || b2 is null || c2 is null || a3 is null || b3 is null || c3 is null || TreeAnalyzer.IsZero(a3)) + return null; + var ratio = (a2 / a3).InnerSimplified; + if (!Functions.PartialFractions.IsZeroAsAValue((b2 - ratio * b3).InnerSimplified) + || !Functions.PartialFractions.IsZeroAsAValue((c2 - ratio * c3).InnerSimplified)) + return null; + var signed = below ? -power : power; + constant *= MathS.Pow(ratio, signed); + plusPower += signed; + minusPower += signed; + } + // Simplified, x-free and small: `-p + p` is a zero power only so. + plusPower = plusPower.Simplify(); + minusPower = minusPower.Simplify(); + Entity written = constant; + if (!TreeAnalyzer.IsZero(plusPower)) + written *= MathS.Pow(1 + MathS.i * argument, plusPower); + if (!TreeAnalyzer.IsZero(minusPower)) + written *= MathS.Pow(1 - MathS.i * argument, minusPower); + return Integration.ComputeAsAQuestionOfItsOwn(written, x, integrateByParts); + } + /// /// An exponential of i times an inverse trigonometric function, written /// algebraically: e^(i arctan(L)) is (1 + i L)/sqrt(1 + L^2), diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 0d874364b..d231623db 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -671,6 +671,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // question asked, before the rules that spent the budget: `e^(3 i atan(a + b x))/x^2` ran out of // time where, rewritten, it is answered in half a second. It is asked again in its place below. if (AnsweringTheQuestionAsked && (answer = IndefiniteIntegralSolver.SolveByWritingAnExponentialOfAnInverseAlgebraically(expr, x, integrateByParts)) is { }) return answer; + // The same for a symbolic multiple of the arctangent, beside powers of its quadratic. + if (AnsweringTheQuestionAsked && (answer = IndefiniteIntegralSolver.SolveAnExponentialOfASymbolicMultipleOfAnArctangentAsPowersOfTheLinears(expr, x, integrateByParts)) is { }) return answer; // A polynomial over a symbolic quartic that splits into two quadratics in a hidden variable, // palindromic or even after a shift: the rules below spent seconds declining both. if ((answer = IndefiniteIntegralSolver.SolveAPolynomialOverASymbolicQuarticThatSplitsIntoTwoQuadratics(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/AnExponentialOfASymbolicMultipleOfAnArctangentIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnExponentialOfASymbolicMultipleOfAnArctangentIntegralTest.cs new file mode 100644 index 000000000..e641d272e --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnExponentialOfASymbolicMultipleOfAnArctangentIntegralTest.cs @@ -0,0 +1,57 @@ +// +// 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 +{ + /// + /// An exponential of a symbolic multiple of i arctan(a x) beside a power of its quadratic, + /// written as powers of the two linears: Rubi's 5.3.6 + /// (c + a^2 c x^2)^p/e^(2 i p arctan(a x)), which was left unevaluated. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AnExponentialOfASymbolicMultipleOfAnArctangentIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + var pinned = integral.Substitute("C", 0); + Entity original = integrand.ToEntity(); + foreach (var (name, value) in pins) + { + pinned = pinned.Substitute(name, value); + original = original.Substitute(name, value); + } + var derivative = pinned.Differentiate("x"); + foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 }) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart); + var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)); + Assert.True(difference / scale < 1e-8, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + + [Theory] + [InlineData("(c+a^2*c*x^2)^p/e^(2*i*p*atan(a*x))")] + [InlineData("e^(2*i*p*atan(a*x))*(c+a^2*c*x^2)^p")] + [InlineData("e^(i*n*atan(a*x))/(c+a^2*c*x^2)")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 1.3), ("c", 0.7), ("p", 0.4), ("n", 0.6)); + DifferentiatesBack(integrand, ("a", -0.5), ("c", -1.5), ("p", 1.3), ("n", -2.2)); + } + } +} From 46b781938f267d91bffe77a739b9b8e6d1db90d3 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 18 Oct 2026 00:01:17 +0000 Subject: [PATCH 2/2] Record the exponentials of a symbolic multiple of i arctan in BREAKING-CHANGES Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index ff524e1a1..d1451be9d 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -607,6 +607,19 @@ partial fractions divide. It is taken out in front of the integral first | `"(b*d + 2*c*d*x)^9/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 76,249 characters, the length the content-free one already had | | `"(b*d + 2*c*d*x)^8/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 65,236 characters | +### An exponential of a symbolic multiple of i arctan is written as powers of the linears + +**Answers where there were none.** `(c + a^2 c x^2)^p/e^(2 i p arctan(a x))` was left unevaluated. For a real `L`, +`e^(k i arctan(L))` is `(1 + i L)^(k/2) (1 - i L)^(-k/2)` and `(K (1 + L^2))^q` is `K^q (1 + i L)^q (1 - i L)^q`, for any +`k` and `q`, so the integrand is a product of powers of two linears, and with these exponents a single power +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(c+a^2*c*x^2)^p/e^(2*i*p*atan(a*x))".ToEntity().Integrate("x")` | `integral(...)` | 84 characters | +| `"e^(2*i*p*atan(a*x))*(c+a^2*c*x^2)^p".ToEntity().Integrate("x")` | `integral(...)` | 86 characters | +| `"e^(i*n*atan(a*x))/(c+a^2*c*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 123 characters | + ### A whole power is taken out of a power of a product, in front of a constant **Answers where there were none.** `(c sin(a + b x)^3)^(1/3)/x` was declined: nothing read a function under a root of