From 5e9f88a6c01d6ff6b627e816d883c47c2802e8d5 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 16 Oct 2026 22:55:09 +0000 Subject: [PATCH 1/2] Integrate a power of x times a power of a function of another power of x under that power Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 61 +++++++++++++++++++ .../Integration/Integration.Definition.cs | 3 + ...rOfXTimesAFunctionOfAnotherIntegralTest.cs | 57 +++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/APowerOfXTimesAFunctionOfAnotherIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 176887f8a..e8f714def 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -26087,6 +26087,67 @@ when two.Equals(EInteger.FromInt32(2)) && e1 == MathS.e && e2 == MathS.e return Functions.PartialFractions.DerivativeDiffersWhereReal(answer, expr, x) ? null : answer; } + /// + /// A power of x times a whole power, past the first, of a sum holding an inverse function or a + /// logarithm of a power of x, x^m (a + b atanh(c x^n))^p, under t = x^n where + /// (m + 1)/n is whole: x^m dx = t^((m + 1)/n - 1) dt/n. + /// + /// + /// The rules before it timed out on Rubi's 5.3.2 (a + b atan(c x^2))^2/x^5 and 7.3.2 + /// x^3 (a + b atanh(c x^2))^2, (a + b atanh(c/x^2))^2/x^5 and + /// (a + b atanh(c x^(3/2)))^2/x^4, each a problem in t they answer in a second. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveAPowerOfXTimesAFunctionOfAnotherUnderThatPower(Entity expr, Entity.Variable x) + { + if (!Integration.AnsweringTheQuestionAsked) + return null; + // The factors of a product and a quotient, each with the sign of its exponent. + 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) } + }; + ERational m = ERational.Zero; + Entity? function = null; + Entity constant = Number.Integer.One; + foreach (var (factor, below) in Factors(expr, false)) + { + if (!factor.ContainsNode(x)) + constant = below ? constant / factor : constant * factor; + else if (factor == x) + m = below ? m - ERational.One : m + ERational.One; + else if (factor is Powf(var b0, Number.Rational { ERational: var r }) && b0 == x) + m = below ? m - r : m + r; + else if (function is null && !below && factor is Powf(var sum, Number.Integer { EInteger: var power }) && power.CompareTo(EInteger.FromInt32(2)) >= 0) + function = factor; + else + return null; + } + if (function is null) + return null; + // The one power of x the function is written in, and an inverse function or a logarithm of it. + var powers = function.Nodes.Where(node => node is Powf(var b1, Number.Rational) && b1 == x).Distinct().ToList(); + if (powers.Count != 1 || powers[0] is not Powf(_, Number.Rational { ERational: var n }) || n.IsZero || n.Equals(ERational.One)) + return null; + var t = Variable.CreateUnique(expr, "t_pow"); + var inT = function.Substitute(powers[0], t); + if (inT.ContainsNode(x) + || !inT.Nodes.Any(node => node is Arcsinf or Arccosf or Arctanf or Arccotanf or Logf && node.ContainsNode(t))) + return null; + // PeterO keeps a quotient as written: -4/2 has the denominator 2 until it is reduced. + var k = ((m + ERational.One) / n).ToLowestTerms(); + if (!k.Denominator.Equals(EInteger.One) || !k.Numerator.CanFitInInt32()) + return null; + var exponent = k.Numerator.ToInt32Unchecked() - 1; + var inTWhole = (exponent == 0 ? inT : MathS.Pow(t, Number.Integer.Create(exponent)) * inT).InnerSimplified; + if (Integration.ComputeAsAQuestionOfItsOwn(inTWhole, t, integrateByParts: true) is not { } integrated) + return null; + var answer = (constant / Number.Rational.Create(n.Numerator, n.Denominator) * integrated.Substitute(t, powers[0])).InnerSimplified; + return answer.Nodes.Any(node => node == MathS.NaN) ? null : answer; + } + /// /// A rational function of a sine, or of a cosine, over a quadratic in it with a symbol /// among the coefficients: sin(x)/(a + b sin(x) + c sin(x)^2), and the same with diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 39247aeed..66cd36d6f 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -670,6 +670,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // A power of a sine, cosine or hyperbolic one over a binomial in a power of it, over the // binomial's roots, before the rules that spent the budget on `sinh(y)^4/(a + b sinh(y)^3)`. if ((answer = IndefiniteIntegralSolver.SolveAPowerOverABinomialInAPowerOfASineOverItsRoots(expr, x)) is { }) return answer; + // A power of x times a power of a sum holding an inverse function of x^n, under t = x^n, + // before the rules that timed out on `x^3 (a + b atanh(c x^2))^2`. + if ((answer = IndefiniteIntegralSolver.SolveAPowerOfXTimesAFunctionOfAnotherUnderThatPower(expr, x)) is { }) return answer; // A rational function times the logarithm of a nested product of powers of linears, by parts, // its logarithm's derivative read off as written: `ln(e (f (a + b x)^p (c + d x)^q)^r)`. if ((answer = IndefiniteIntegralSolver.SolveARationalFunctionTimesALogarithmOfPowersOfLinears(expr, x, integrateByParts)) is { }) return answer; diff --git a/Sources/Tests/UnitTests/Calculus/APowerOfXTimesAFunctionOfAnotherIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerOfXTimesAFunctionOfAnotherIntegralTest.cs new file mode 100644 index 000000000..78e3c8eba --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerOfXTimesAFunctionOfAnotherIntegralTest.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 +{ + /// + /// A power of x times a power of a sum holding an inverse function of another power of x, under + /// t = x^n: Rubi's 5.3.2 and 7.3.2 x^m (a + b atanh(c x^n))^2, which timed out. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class APowerOfXTimesAFunctionOfAnotherIntegralTest + { + 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("(a+b*atan(c*x^2))^2/x^5")] + [InlineData("x^3*(a+b*atanh(c*x^2))^2")] + [InlineData("(a+b*atanh(c/x^2))^2/x^5")] + [InlineData("x^11*(a+b*atanh(c*x^3))^2")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 1.3), ("b", 0.7), ("c", 0.3)); + DifferentiatesBack(integrand, ("a", -2), ("b", 1.5), ("c", -0.2)); + } + } +} From 4ed0a3befcd48465ed22c4c9f9331d1d7503de21 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 17 Oct 2026 08:14:22 +0000 Subject: [PATCH 2/2] Record the powers of x times a power of a function of another power 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 e89c879dd..66bfaa7fd 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -312,6 +312,20 @@ an arm for each sign of a quantity that is not real, `x^2 provided not x = 0` be | `"piecewise(2 * x provided not x = 0, 3 * x ^ 2 provided i - 1 < 0)".ToEntity().InnerSimplified` | `NaN` | `2 * x` where `x` is not 0 | | `"piecewise(1 provided i < 0, 2 provided i > 0)".ToEntity().InnerSimplified` | `NaN` | `NaN`, as before: no case holds | +### A power of x times a power of a function of another power of x is integrated under that power + +**Answers where there were none.** `x^3 (a + b atanh(c x^2))^2` ran out of time, and so did ten more of Rubi's 3.4, 5.3.2 +and 7.3.2. `x^m F(x^n)^p`, with `F` a sum holding an inverse tangent, inverse hyperbolic tangent or logarithm of +`x^n` and `p` at least 2, is now integrated under `t = x^n` wherever `(m + 1)/n` is whole +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x^3*(a+b*atanh(c*x^2))^2".ToEntity().Integrate("x")` | `integral(...)` | 308 characters | +| `"(a+b*atan(c*x^2))^2/x^5".ToEntity().Integrate("x")` | `integral(...)` | 870 characters | +| `"(a+b*atanh(c*x^(3/2)))^2/x^4".ToEntity().Integrate("x")` | `integral(...)` | 330 characters | +| `"(a+b*ln(c*(d+k/x^(2/3))^n))^2/x^5".ToEntity().Integrate("x")` | `integral(...)` | 2,034 characters | + ### Zero times a power with a non-real exponent is zero where the power is defined **A value where there was NaN.** `0 b^i` simplified to NaN, and so did `b^i - b^i`. That in turn made `x b^i`'s