From 5e11a02db2242578bdc6b22855ae14568bbfdcec Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 14 Oct 2026 02:40:12 +0000 Subject: [PATCH 1/3] Take powers of linears apart beside a symbolic root of a quadratic Over linears alone the split left the integrand to Euler's substitution, which writes the root of a symbolic leading coefficient into every coefficient and declined sqrt(a + b x + c x^2)/(x^2 (d + e x)). With a symbol in the integrand it is taken apart, and Rubi 1.2.2.4's sqrt(a + b x^2 + c x^4)/(x^3 (d + e x^2)) is one under u = x^2. Part of #718 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 6 +- ...fLinearsBesideASymbolicRootIntegralTest.cs | 72 +++++++++++++++++++ 2 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/PowersOfLinearsBesideASymbolicRootIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 7d0e7556d..39ab03cf4 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -18594,8 +18594,10 @@ static Entity WithoutConditions(Entity coefficient) // the linears alone the reciprocal writes a sign of each, `sgn(sqrt(1 + x) - 1)` once // `sqrt(x + sqrt(1 + x))/x^2` is taken back from `u = sqrt(1 + x)`, whose derivative // is not read where the argument is not shown real -- and Euler's substitution, after - // this, answers those without one. - if (beyondTheLinears && !aQuadratic) + // this, answers those without one. With a symbol in them it does not: the leading + // coefficient's root is in every coefficient it writes, and `sqrt(a + b x + c x^2)/(x^2 (d + e x))`, + // which is Rubi's 1.2.2.4 under `u = x^2`, was declined. + if (beyondTheLinears && !aQuadratic && !expr.Vars.Any(symbol => symbol != x)) return null; // Distinct: two factors with the same root are one repeated, which is not this. // Beyond the linears the split decides that, and merges linears with one root. diff --git a/Sources/Tests/UnitTests/Calculus/PowersOfLinearsBesideASymbolicRootIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/PowersOfLinearsBesideASymbolicRootIntegralTest.cs new file mode 100644 index 000000000..83cb4b5c7 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/PowersOfLinearsBesideASymbolicRootIntegralTest.cs @@ -0,0 +1,72 @@ +// +// 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 rational function with powers of linears below the bar, beside a half-odd power of a + /// quadratic with symbols in it, taken apart over the linears: Euler's substitution, which + /// the split left these to, writes the root of a symbolic leading coefficient into every + /// coefficient. Rubi's 1.2.2.4 sqrt(a + b x^2 + c x^4)/(x^3 (d + e x^2)) is one under + /// u = x^2. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class PowersOfLinearsBesideASymbolicRootIntegralTest + { + private static void DifferentiatesBack(string integrand, double[] points, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Assert.DoesNotContain("NaN", integral.Stringize()); + + // Pinned before it is differentiated: the answer carries `sgn(x + d/e)`, whose + // derivative is read where its argument is a number. + 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"); + + var compared = 0; + foreach (var at in points) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + if (got.IsNaN || want.IsNaN) + continue; + compared++; + 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}"); + } + Assert.True(compared >= 3, + $"only {compared} of {points.Length} points were comparable for {integrand}"); + } + + [Theory] + [InlineData("sqrt(a+b*x+c*x^2)/(x^2*(d+e*x))")] + [InlineData("1/(x^2*(d+e*x)*sqrt(a+b*x+c*x^2))")] + [InlineData("sqrt(a+b*x^2+c*x^4)/(x^3*(d+e*x^2))")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + var points = new[] { -1.4, -0.3, 0.8, 2.25 }; + DifferentiatesBack(integrand, points, ("a", 2), ("b", 1.0 / 3), ("c", 1.5), ("d", 1.25), ("e", 3)); + DifferentiatesBack(integrand, points, ("a", -1), ("b", 3), ("c", 2), ("d", -2), ("e", 1.5)); + } + } +} From 7093bb1487b2ea3274829866fed1e8ec5bb96c8b Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 14 Oct 2026 04:28:22 +0000 Subject: [PATCH 2/3] Pin a symbol, not Euler's number, in the split's test Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- ...ersOfLinearsBesideASymbolicRootIntegralTest.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/Sources/Tests/UnitTests/Calculus/PowersOfLinearsBesideASymbolicRootIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/PowersOfLinearsBesideASymbolicRootIntegralTest.cs index 83cb4b5c7..ff141a6d6 100644 --- a/Sources/Tests/UnitTests/Calculus/PowersOfLinearsBesideASymbolicRootIntegralTest.cs +++ b/Sources/Tests/UnitTests/Calculus/PowersOfLinearsBesideASymbolicRootIntegralTest.cs @@ -16,7 +16,7 @@ namespace AngouriMath.Tests.Calculus /// quadratic with symbols in it, taken apart over the linears: Euler's substitution, which /// the split left these to, writes the root of a symbolic leading coefficient into every /// coefficient. Rubi's 1.2.2.4 sqrt(a + b x^2 + c x^4)/(x^3 (d + e x^2)) is one under - /// u = x^2. + /// u = x^2, written here with k for e, which is Euler's number. /// #718 /// [Trait("Area", "Calculus")] @@ -28,7 +28,7 @@ private static void DifferentiatesBack(string integrand, double[] points, params Assert.DoesNotContain("integral(", integral.Stringize()); Assert.DoesNotContain("NaN", integral.Stringize()); - // Pinned before it is differentiated: the answer carries `sgn(x + d/e)`, whose + // Pinned before it is differentiated: the answer carries `sgn(x + d/k)`, whose // derivative is read where its argument is a number. var pinned = integral.Substitute("C", 0); Entity original = integrand.ToEntity(); @@ -59,14 +59,15 @@ private static void DifferentiatesBack(string integrand, double[] points, params } [Theory] - [InlineData("sqrt(a+b*x+c*x^2)/(x^2*(d+e*x))")] - [InlineData("1/(x^2*(d+e*x)*sqrt(a+b*x+c*x^2))")] - [InlineData("sqrt(a+b*x^2+c*x^4)/(x^3*(d+e*x^2))")] + [InlineData("sqrt(a+b*x+c*x^2)/(x^2*(d+k*x))")] + [InlineData("1/(x^2*(d+k*x)*sqrt(a+b*x+c*x^2))")] + [InlineData("sqrt(a+b*x^2+c*x^4)/(x^3*(d+k*x^2))")] + [InlineData("1/((d+k*x)*(f+g*x)^2*sqrt(a+b*x+c*x^2))")] public void IsAnsweredAndDifferentiatesBack(string integrand) { var points = new[] { -1.4, -0.3, 0.8, 2.25 }; - DifferentiatesBack(integrand, points, ("a", 2), ("b", 1.0 / 3), ("c", 1.5), ("d", 1.25), ("e", 3)); - DifferentiatesBack(integrand, points, ("a", -1), ("b", 3), ("c", 2), ("d", -2), ("e", 1.5)); + DifferentiatesBack(integrand, points, ("a", 2), ("b", 1.0 / 3), ("c", 1.5), ("d", 1.25), ("k", 3), ("f", 7.0 / 3), ("g", -0.4)); + DifferentiatesBack(integrand, points, ("a", -1), ("b", 3), ("c", 2), ("d", -2), ("k", 1.5), ("f", 0.5), ("g", 5.0 / 3)); } } } From 51bce75db2d7d57a5fd92403e9fef47241a4eeb4 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 14 Oct 2026 04:29:53 +0000 Subject: [PATCH 3/3] Record the answers the split over powers of linears gives Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 28eb32b99..04c61b66e 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -555,6 +555,30 @@ same answer as on the unreleased master, in 1.4 seconds rather than 30 | `"1/((d + k*x)*(f + g*x)*(a + b*x + c*x^2)^2)".ToEntity().Integrate("x")` | `integral(...)` | 24,143 characters | | `"1/((d + k*x)^3*(a + b*x + c*x^2)^2)".ToEntity().Integrate("x")` | `integral(...)` | 9,593 characters | +### Powers of linears beside a symbolic root of a quadratic are taken apart over the linears + +**Answers where there were none.** `sqrt(a + b x + c x^2)/((d + k x)(f + g x)^2)` was declined: with no quadratic +below the bar, powers of linears were left to Euler's substitution, which writes the root of a symbolic leading +coefficient into every coefficient and does not finish. They are taken apart over the linears now where the +integrand has a symbol in it, each piece closed beside the root. The answers are large because each piece's cases, +by the sign of the radicand at its pole and of `c`, multiply in the sum +([#1874](https://github.com/asc-community/AngouriMath/issues/1874)): the fourth row is 27 cases of about 3,000 +characters each ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"sqrt(a + b*x + c*x^2)/((d + k*x)*(f + g*x)^2)".ToEntity().Integrate("x")` | `integral(...)` | 12,694 characters | +| `"sqrt(a + b*x + c*x^2)/((d + k*x)*(f + g*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 20,029 characters | +| `"sqrt(a + b*x + c*x^2)/((d + k*x)*(f + g*x)^4)".ToEntity().Integrate("x")` | `integral(...)` | 29,203 characters | +| `"(a + b*x + c*x^2)^(3/2)/((d + k*x)*(f + g*x)^3)".ToEntity().Integrate("x")` | `integral(...)` | 79,744 characters | +| `"1/((d + k*x)*(f + g*x)^2*sqrt(a + b*x + c*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 11,335 characters | +| `"1/((d + k*x)*(f + g*x)^3*sqrt(a + b*x + c*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 18,103 characters | +| `"sqrt(a + b*x^2 + c*x^4)/(x^3*(d + k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 5,902 characters | +| `"(a + b*x^2 + c*x^4)^(3/2)/(x^3*(d + k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 27,184 characters | +| `"1/(x^3*(d + k*x^2)*sqrt(a + b*x^2 + c*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 5,515 characters | +| `"asec(a + b*x)/x^4".ToEntity().Integrate("x")` | `integral(...)` | 11,873 characters | +| `"acsc(a + b*x)/x^4".ToEntity().Integrate("x")` | `integral(...)` | 11,864 characters | + ### A trinomial the numerator holds is divided out of a power of it **A shorter answer, where 2.5.0 had none.** `(d + g x + f x^2)(a + b x^2 + c x^4)/(a + b x^2 + c x^4)^4`, written