From d235a4041d516c472364b1e9becfb2e114dc306a Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 16 Oct 2026 13:54:04 +0000 Subject: [PATCH 1/2] Integrate a function of an inverse times its derivative up to a factor constant though written with x atan(c x/sqrt(a - c^2 x^2)) has the derivative c/sqrt(a - c^2 x^2), and beside it 1/sqrt(d - c^2 d x^2/a) is that times a factor constant wherever it is real. With u the inverse, the integrand over u' splits into a function of u and that factor, and the integral is the factor times the integral of the function. The factor is taken as constant where it holds its value across sampled points; asking Simplify for its derivative took four seconds. Rubi's 5.3.7 atan(c x/sqrt(a - c^2 x^2))^m/sqrt(d - c^2 d x^2/a) and eleven more were declined, master spending up to 28 seconds on one. 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 | 56 +++++++++++++++++ .../Integration/Integration.Definition.cs | 5 ++ ...AnInverseTimesItsDerivativeIntegralTest.cs | 61 +++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/AFunctionOfAnInverseTimesItsDerivativeIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 1c6458f74..8b5f9b987 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -29071,6 +29071,62 @@ static bool Through(Entity node, Entity.Variable x) } } + /// + /// A function of an inverse sine, cosine or tangent u of something not linear, times + /// u' up to a factor that is constant though written with x in it: + /// integral G(u) K u' dx = K integral G(t) dt at t = u. + /// atan(c x/sqrt(a - c^2 x^2)) has the derivative c/sqrt(a - c^2 x^2), and beside it + /// 1/sqrt(d - c^2 d x^2/a) is that over c times sqrt(a - c^2 x^2)/sqrt(d - c^2 d x^2/a), + /// which is constant wherever it is real. + /// + /// + /// The factor is taken as constant only where its derivative is zero as a value. Rubi's 5.3.7 + /// atan(c x/sqrt(a - c^2 x^2))^m/sqrt(d - c^2 d x^2/a) and the like were declined. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveAFunctionOfAnInverseTimesItsDerivativeUpToAConstant(Entity expr, Entity.Variable x, bool integrateByParts) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + var inverses = expr.Nodes.Where(node => node is Arctanf or Arcsinf or Arccosf && node.ContainsNode(x) + && !TreeAnalyzer.TryGetPolyLinear(((Function)node).DirectChildren.First(), x, out _, out _)).Distinct().ToList(); + foreach (var inverse in inverses) + { + var t = Variable.CreateUnique(expr, "t_inv"); + var inT = expr.Substitute(inverse, t); + if (!inT.ContainsNode(t)) + continue; + var derivative = inverse.Differentiate(x).Simplify(); + if (Functions.PartialFractions.IsZeroAsAValue(derivative)) + continue; + var (top, bottom) = Functions.SingleQuotient.Of((inT / derivative).InnerSimplified); + Entity inTheVariable = Number.Integer.One, constant = Number.Integer.One; + foreach (var (side, below) in new[] { (top, false), (bottom, true) }) + foreach (var factor in Mulf.LinearChildren(side)) + { + if (factor.ContainsNode(t)) + inTheVariable = below ? inTheVariable / factor : inTheVariable * factor; + else + constant = below ? constant / factor : constant * factor; + } + if (inTheVariable.ContainsNode(x) || !constant.ContainsNode(x) || !HoldsItsValue(constant)) + continue; + if (Integration.ComputeAsAQuestionOfItsOwn(inTheVariable, t, integrateByParts) is not { } inU) + continue; + var answer = (constant * inU.Substitute(t, inverse)).InnerSimplified; + if (!Functions.PartialFractions.DerivativeDiffersWhereReal(answer, expr, x)) + return answer; + } + return null; + + // Constant where it takes its value at x = 3/10 at the sampled points, every other symbol + // pinned: asking Simplify whether the derivative of the factor vanished took four seconds + // on `sqrt(-a e^2/b - e^2 x^2)/(e sqrt(a + b x^2))`, and its square stayed a power of a + // quotient of roots. A point it cannot tell at counts against it; the answer is checked too. + bool HoldsItsValue(Entity factor) + => Functions.PartialFractions.HoldsAtSampledPoints(factor, factor.Substitute(x, Number.Rational.Create(3, 10)), x); + } + /// /// 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 8b11c7b49..176741cf2 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -664,6 +664,11 @@ 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; + // A function of an inverse of something not linear times its derivative up to a factor + // constant though written with x: `atan(c x/sqrt(a - c^2 x^2))^2/sqrt(d - c^2 d x^2/a)`. Early, + // since it fires only where that factor holds x, and the rules before it spent 28 seconds on + // `atan(e x/sqrt(-a e^2/b - e^2 x^2))/sqrt(a + b x^2)` declining. + if ((answer = IndefiniteIntegralSolver.SolveAFunctionOfAnInverseTimesItsDerivativeUpToAConstant(expr, x, integrateByParts)) is { }) return answer; // A whole power of a linear in the inverse sine, cosine or hyperbolic cosine of // `c + d x^2` with `c^2 = 1`, by its recurrence, which closes: by parts it went round. if ((answer = IndefiniteIntegralSolver.SolveAPowerOfAnInverseFunctionOfAQuadraticByTheRecurrence(expr, x)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/AFunctionOfAnInverseTimesItsDerivativeIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AFunctionOfAnInverseTimesItsDerivativeIntegralTest.cs new file mode 100644 index 000000000..f8d13aba7 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AFunctionOfAnInverseTimesItsDerivativeIntegralTest.cs @@ -0,0 +1,61 @@ +// +// 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 function of an inverse tangent of something not linear times its derivative, up to a factor + /// constant though written with x: Rubi's 5.3.7 atan(c x/sqrt(a - c^2 x^2))^m/sqrt(d - c^2 d x^2/a), + /// where sqrt(a - c^2 x^2)/sqrt(d - c^2 d x^2/a) is that factor. Pinned on the real domain. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AFunctionOfAnInverseTimesItsDerivativeIntegralTest + { + 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("atan(c*x/sqrt(a-c^2*x^2))^m/sqrt(d-c^2*d*x^2/a)")] + [InlineData("atan(c*x/sqrt(a-c^2*x^2))^2/sqrt(d-c^2*d*x^2/a)")] + [InlineData("1/(atan(c*x/sqrt(a-c^2*x^2))*sqrt(d-c^2*d*x^2/a))")] + [InlineData("1/(atan(k*x/sqrt(-a*k^2/b-k^2*x^2))^3*sqrt(a+b*x^2))")] + [InlineData("atan(k*x/sqrt(-a*k^2/b-k^2*x^2))/sqrt(a+b*x^2)")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 9), ("b", -1.25), ("c", 0.7), ("d", 1.4), ("k", 0.75), ("m", 2.5)); + DifferentiatesBack(integrand, ("a", 7), ("b", -2), ("c", -0.5), ("d", 0.6), ("k", -0.3), ("m", 1.0 / 3)); + } + } +} From 6f26ab3792d4aaedfb923c30dc8b52502db108b3 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 16 Oct 2026 16:38:05 +0000 Subject: [PATCH 2/2] Record the inverse tangent substitution in the breaking changes Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 927c9681c..6df70df1d 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -396,6 +396,22 @@ in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take | `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters | | `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters | +### A function of an inverse tangent times its derivative up to a constant factor is integrated by substitution + +**Answers where there were none.** `atan(c x/sqrt(a - c^2 x^2))^m/sqrt(d - c^2 d x^2/a)` was declined, and so were +eleven more of Rubi's 5.3.7. The derivative of `u = atan(c x/sqrt(a - c^2 x^2))` is `c/sqrt(a - c^2 x^2)`, and beside +it `1/sqrt(d - c^2 d x^2/a)` is that times a factor constant wherever it is real, though written with `x` in it. The +integrand over `u'` is now split into a function of `u` and such a factor, and the integral is the factor times the +integral of the function ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"atan(c*x/sqrt(a-c^2*x^2))^m/sqrt(d-c^2*d*x^2/a)".ToEntity().Integrate("x")` | `integral(...)` | 176 characters | +| `"atan(c*x/sqrt(a-c^2*x^2))/sqrt(d-c^2*d*x^2/a)".ToEntity().Integrate("x")` | `integral(...)` | 164 characters | +| `"1/(atan(c*x/sqrt(a-c^2*x^2))*sqrt(d-c^2*d*x^2/a))".ToEntity().Integrate("x")` | `integral(...)` | 160 characters | +| `"atan(k*x/sqrt(-a*k^2/b-k^2*x^2))^m/sqrt(a+b*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 320 characters | +| `"1/(atan(k*x/sqrt(-a*k^2/b-k^2*x^2))^3*sqrt(a+b*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 314 characters | + ### Half an odd power of an inverse hyperbolic cosine of `c + d x^2` is integrated in error functions **Answers where there were none.** `(a + b acosh(1 + d x^2))^(1/2)` was declined, and so were thirteen more of Rubi's