From a294a9c451009f01eaf49c79c7d8527f36368aa9 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 16 Oct 2026 22:05:24 +0000 Subject: [PATCH 1/2] Integrate an arctangent over a power of d (1 +- i c x) under the tangent Under x = tan(u)/c, 1 + i tan(u) is e^(i u)/cos(u), so x^m (a + b atan(c x))^p/(d (1 +- i c x))^k is (a + b u)^p (tan(u)/c)^m d^(-k) e^(-+i k u) cos(u)^(k - 2)/c, a polynomial in u times exponentials and trigonometric functions. Rubi's 5.3.4 (a + b atan(c x))^2/(d + i c d x)^2 and seven like it ran out of time. Part of https://github.com/asc-community/AngouriMath/issues/718 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 64 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 3 + ...ngentOverAPowerOfOnePlusICXIntegralTest.cs | 59 +++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/AnArctangentOverAPowerOfOnePlusICXIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 176887f8a..d60228250 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -29364,6 +29364,70 @@ bool HoldsItsValue(Entity factor) return (alpha / (p0 * slope) * product).InnerSimplified; } + /// + /// x^m (a + b atan(c x))^p over a power of d (1 +- i c x), under x = tan(u)/c, where + /// 1 + i tan(u) is e^(i u)/cos(u): the integrand in u is + /// (a + b u)^p (tan(u)/c)^m d^(-k) e^(-+i k u) cos(u)^k/(c cos(u)^2), a polynomial in u times + /// exponentials and trigonometric functions, and the answer is written back at u = atan(c x). + /// + /// + /// 1 + i tan(u) = (cos(u) + i sin(u))/cos(u) holds for every u where the tangent is defined, + /// and atan(c x) takes each real x into (-pi/2, pi/2), where it is. Rubi's 5.3.4 + /// (a + b atan(c x))^2/(d + i c d x)^2 and seven like it ran out of time. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveAnArctangentOverAPowerOfOnePlusICXUnderTheTangent(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + var (numerator, denominator) = Functions.SingleQuotient.Of(expr); + Arctanf? arctangent = null; + Entity? rate = null; + foreach (var node in numerator.Nodes) + if (node is Arctanf found && found.ContainsNode(x)) + { + if (arctangent is not null && arctangent != found) + return null; + if (!TreeAnalyzer.TryGetPolyLinear(found.Argument, x, out var slope, out var offset) || !Functions.PartialFractions.IsZeroAsAValue(offset)) + return null; + (arctangent, rate) = (found, slope); + } + if (arctangent is null || rate is null) + return null; + // Below the bar one power of a linear d (1 + s i c x), s = +-1, and nothing else with x. + Entity? linear = null; + var k = 0; + Entity constant = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(denominator)) + { + if (!factor.ContainsNode(x)) { constant *= factor; continue; } + var (b, e) = factor is Powf(var pb, Number.Integer { EInteger: var pe }) && pe.Sign > 0 && pe.CanFitInInt32() ? (pb, pe.ToInt32Unchecked()) : (factor, 1); + if (linear is not null) + return null; + (linear, k) = (b, e); + } + if (linear is null || !TreeAnalyzer.TryGetPolyLinear(linear, x, out var lSlope, out var lOffset) || Functions.PartialFractions.IsZeroAsAValue(lOffset)) + return null; + // The ratio of slope to constant is +-i c. + var ratio = (lSlope / (lOffset * MathS.i * rate)).InnerSimplified; + int sign; + if (Functions.PartialFractions.IsZeroAsAValue((ratio - 1).InnerSimplified)) sign = 1; + else if (Functions.PartialFractions.IsZeroAsAValue((ratio + 1).InnerSimplified)) sign = -1; + else return null; + var u = Variable.CreateUnique(expr, "u_atan"); + // Above the bar: a power of x and the function of the arctangent. + var above = numerator.Substitute(arctangent, u); + if (above.Substitute(x, Number.Integer.Zero).ContainsNode(x)) + return null; + var inU = above.Substitute(x, MathS.Tan(u) / rate) + * MathS.Pow(lOffset, Number.Integer.Create(-k)) * MathS.Pow(MathS.e, -sign * k * MathS.i * u) * (k == 2 ? Number.Integer.One : MathS.Pow(MathS.Cos(u), Number.Integer.Create(k - 2))) + / (constant * rate); + if (Integration.ComputeAsAQuestionOfItsOwn(inU.InnerSimplified, u, integrateByParts) is not { } integrated) + return null; + var answer = integrated.Substitute(u, arctangent).InnerSimplified; + return Functions.PartialFractions.DerivativeDiffersWhereReal(answer, expr, x) ? null : answer; + } + /// /// A power of a constant multiple of a radicand that a logarithm holds under a root, /// written over that radicand: acosh(c x) is ln(c x + sqrt(c^2 x^2 - 1)), diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 39247aeed..d35dd9226 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -664,6 +664,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // A sum free of the variable that comes to a number multiplied out, as that number, // before anything reads a coefficient or an exponent in it: `2 + 2 a - 2 (1 + a)`. if ((answer = IndefiniteIntegralSolver.SolveByFoldingConstantSumsToTheirNumbers(expr, x, integrateByParts)) is { }) return answer; + // An arctangent over a power of d (1 +- i c x), under x = tan(u)/c, where 1 + i tan(u) is + // e^(i u)/cos(u): the rules below ran out of time on `(a + b atan(c x))^2/(d + i c d x)^2`. + if ((answer = IndefiniteIntegralSolver.SolveAnArctangentOverAPowerOfOnePlusICXUnderTheTangent(expr, x, integrateByParts)) is { }) return answer; // A symbolic power of a sine, cosine, secant or cosecant times `alpha + beta f^2`, where it is // the derivative of `cos sin^(m + 1)`, `sin cos^(m + 1)`, `tan sec^m` or `cot csc^m`. if ((answer = IndefiniteIntegralSolver.SolveASymbolicPowerOfATrigonometricFunctionAsTheDerivativeOfAProduct(expr, x)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/AnArctangentOverAPowerOfOnePlusICXIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnArctangentOverAPowerOfOnePlusICXIntegralTest.cs new file mode 100644 index 000000000..034341b47 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnArctangentOverAPowerOfOnePlusICXIntegralTest.cs @@ -0,0 +1,59 @@ +// +// 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 +{ + /// + /// x^m (a + b atan(c x))^p over a power of d (1 +- i c x), under x = tan(u)/c, where + /// 1 + i tan(u) is e^(i u)/cos(u): Rubi's 5.3.4. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AnArctangentOverAPowerOfOnePlusICXIntegralTest + { + 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}"); + } + } + + // d with the sign that keeps the argument real at every sampled x, and |d| below 2/2.3^2 + // so that the inverse sine and cosine stay inside [-1, 1]; off it the exact checks hold too. + [Theory] + [InlineData("(a+b*atan(c*x))^2/(d+i*c*d*x)^2")] + [InlineData("x*(a+b*atan(c*x))^2/(d+i*c*d*x)^3")] + [InlineData("(a+b*atan(c*x))^3/(d+i*c*d*x)^4")] + [InlineData("(a+b*atan(c*x))/(d-i*c*d*x)^3")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 0.6), ("b", 1.25), ("c", 0.4), ("d", 1.5)); + DifferentiatesBack(integrand, ("a", -1.5), ("b", -0.25), ("c", -2), ("d", 0.4)); + } + } +} From 33e9854601f847130003760866593efa981e34e4 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 17 Oct 2026 04:30:36 +0000 Subject: [PATCH 2/2] Record the arctangents over a power of d (1 + i c x) answered in BREAKING-CHANGES Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dee44a680..50801077f 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -175,6 +175,20 @@ the working digits, or `MathS.Settings.PrecisionErrorZeroRange` where a caller h still that integer, so `e^(-123.456)` is 0 as it was. [#1338](https://github.com/asc-community/AngouriMath/issues/1338). +### An arctangent over a power of `d (1 + i c x)` is integrated under the tangent + +**Answers where there were none.** `(a + b atan(c x))^2/(d + i c d x)^2` ran out of time, and so did seven more of +Rubi's 5.3.4. Under `x = tan(u)/c`, `1 + i tan(u)` is `e^(i u)/cos(u)`, so `x^m (a + b atan(c x))^p/(d (1 ± i c x))^k` +becomes a polynomial in `u` times exponentials and trigonometric functions, and is now integrated so +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(a+b*atan(c*x))^2/(d+i*c*d*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 267 characters | +| `"x*(a+b*atan(c*x))^2/(d+i*c*d*x)^3".ToEntity().Integrate("x")` | `integral(...)` | 671 characters | +| `"(a+b*atan(c*x))^2/(1+i*c*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 731 characters | +| `"(a+b*atan(c*x))^3/(d+i*c*d*x)^4".ToEntity().Integrate("x")` | `integral(...)` | 1,202 characters | + ### A constant or an exponential evaluated at a higher precision than before has all of its digits **More digits where there were a hundred.** `pi`, `e` and an exponential, evaluated once at one precision, went on