From 940ac8cc3e0f0fe8950448363b6cd4bebf914b04 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 14 Oct 2026 20:46:01 +0000 Subject: [PATCH 1/4] Split a power of a product beside a whole power of a factor, and ask it early A whole power of a product, or a sum standing as a factor, is split beside a root of one of its factors, and a root of a product beside a whole power of one; two whole powers are a rational function and left to the partial fractions. Asked right after each linear is written once: what reads the product whole took 27 seconds on Rubi's 1.2.1.2:2218 and answered :2276 in 29,285 characters. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 16 ++++- .../Integration/Integration.Definition.cs | 9 ++- ...tBesideAWholePowerOfAFactorIntegralTest.cs | 60 +++++++++++++++++++ 3 files changed, 81 insertions(+), 4 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/APowerOfAProductBesideAWholePowerOfAFactorIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index fa107a045..bfb930def 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -19358,12 +19358,16 @@ Entity TakeOut(Entity node) foreach (var product in roots) foreach (var factor in roots) { + // One of the two a root or a symbolic power at least: two whole powers are a + // rational function, which the partial fractions take apart already. if (ReferenceEquals(product.Node, factor.Node) || product.Base == factor.Base + || product.Exponent is Number.Integer && factor.Exponent is Number.Integer || ExactlyDivided(product.Base, factor.Base, x) is not { } cofactor || !cofactor.ContainsNode(x)) continue; var multiple = Variable.CreateUnique(expr, "k_split"); var written = MathS.Pow(factor.Base, product.Exponent) * MathS.Pow(cofactor, product.Exponent); - var constant = product.Node / written; + // A whole power of a product is the product of the powers, with no constant. + Entity constant = product.Exponent is Number.Integer ? Number.Integer.One : product.Node / written; var rewritten = expr.Replace(node => ReferenceEquals(node, product.Node) ? multiple * written : node).InnerSimplified; Entity? answer; splittingARootOfAProduct = true; @@ -19402,6 +19406,16 @@ void Gather(Entity node) && (exponent is Number.Rational || exponent.Vars.Any()): roots.Add((node, @base, exponent)); break; + // And a whole power, or a sum standing as a factor, beside a root: Rubi's + // 1.2.1.2:2218, `1/((d + e x)^4 (a d e + (c d^2 + a e^2) x + c d e x^2)^(3/2))`, is + // `(d + e x)^(-11/2) (a e + c d x)^(-3/2)` up to the constant, and :2280 has the + // product to the first power below the bar beside `sqrt(d + e x)`. + case Powf(var @base, Number.Integer { IsZero: false } whole) when @base is Sumf or Minusf && @base.ContainsNode(x): + roots.Add((node, @base, whole)); + break; + case Sumf or Minusf when node.ContainsNode(x): + roots.Add((node, node, Number.Integer.One)); + break; } } } diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 3ccccf2e2..8c9302082 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -666,6 +666,12 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // the question asked only, and the polynomial term below writes `1/u` as `u^(-1)` and // asks that a level down: `1/(e^atanh(a x) (c - c/(a x))^(3/2))` reached it nowhere. if ((answer = IndefiniteIntegralSolver.SolveByWritingEachLinearOnce(expr, x, integrateByParts)) is { }) return answer; + // A root or a power of a product beside a power of one of its factors, the product + // split: `(a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4)` is `K (a + b x^2)^2/sqrt(a - b x^2)`. + // Early, since what reads the product whole is slower and longer: Rubi's 1.2.1.2:2218, + // `1/((d + e x)^4 (a d e + (c d^2 + a e^2) x + c d e x^2)^(3/2))`, took 27 seconds to + // reach it and its split form takes a quarter of one. + if ((answer = IndefiniteIntegralSolver.SolveBySplittingARootOfAProductBesideARootOfAFactor(expr, x, integrateByParts)) is { }) return answer; // A symbolic trinomial in x^2 below the bar beside a root of a quadratic, over its roots // in x^2, before anything reads the trinomial whole: asked of it, the routes below ran // past a minute on Rubi's 1.2.2.4 `sqrt(d + e x^2)/(x^2 (a + b x^2 + c x^4))`. @@ -1239,9 +1245,6 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // And a power of a product with a factor under a whole power, that power taken out in // front of a constant: `(c sin(a + b x)^3)^(1/3)` is `K sin(a + b x)`. if ((answer = IndefiniteIntegralSolver.SolveByTakingTheWholePowersOutOfAPowerOfAProduct(expr, x, integrateByParts)) is { }) return answer; - // And a root of a product beside a root of one of its factors, the product split: - // `(a + b x^2)^(5/2)/sqrt(a^2 - b^2 x^4)` is `K (a + b x^2)^2/sqrt(a - b x^2)`. - if ((answer = IndefiniteIntegralSolver.SolveBySplittingARootOfAProductBesideARootOfAFactor(expr, x, integrateByParts)) is { }) return answer; // Product-to-sum among the rewrites rather than before them, because a product of // trigonometric functions of *equal* arguments is a power and wants a different tool; // this only fires where the arguments differ, which is exactly what every substitution diff --git a/Sources/Tests/UnitTests/Calculus/APowerOfAProductBesideAWholePowerOfAFactorIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerOfAProductBesideAWholePowerOfAFactorIntegralTest.cs new file mode 100644 index 000000000..bd0494b29 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerOfAProductBesideAWholePowerOfAFactorIntegralTest.cs @@ -0,0 +1,60 @@ +// +// 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 root of a product beside a whole power of one of its factors, or the product to a whole + /// power beside a root of a factor: split, the powers of the factor gather. Rubi's 1.2.1.2 + /// 1/((d + e x)^4 (a d e + (c d^2 + a e^2) x + c d e x^2)^(3/2)), written here with + /// k for e, which is Euler's number. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class APowerOfAProductBesideAWholePowerOfAFactorIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + // Pinned before it is differentiated: the answer carries the constant the split takes + // out, whose derivative is read where its arguments are numbers. + 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("1/((d+k*x)^4*(a*d*k+(c*d^2+a*k^2)*x+c*d*k*x^2)^(3/2))")] + [InlineData("1/((d+k*x)^(1/2)*(a*d*k+(c*d^2+a*k^2)*x+c*d*k*x^2))")] + [InlineData("(d+k*x)^(7/2)/(a*d*k+(c*d^2+a*k^2)*x+c*d*k*x^2)")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("a", 2), ("c", 1.5), ("d", 1.25), ("k", 2)); + DifferentiatesBack(integrand, ("a", -1), ("c", 2), ("d", -0.5), ("k", 1.5)); + } + } +} From a1fdd28fa7c7ee0e479bd4f4e5f6a6488da3d6dc Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 14 Oct 2026 22:39:15 +0000 Subject: [PATCH 2/4] Take a factor to a whole power of two or more only Beside a bare factor the root was split where it read better whole: Welz's (1 + x)/((x - 2) sqrt(1 + x^3)) became three logarithms, and (1 + a x) sqrt(1 - a^2 x^2)/(1 - a x) went from 4 ms to 36 s. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Continuous/Integration/IndefiniteIntegralSolver.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index bfb930def..dc0b4f1d8 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -19359,9 +19359,13 @@ Entity TakeOut(Entity node) foreach (var factor in roots) { // One of the two a root or a symbolic power at least: two whole powers are a - // rational function, which the partial fractions take apart already. + // rational function, which the partial fractions take apart already. And a + // factor to a whole power of two or more: beside a bare one the root was split + // where it read better whole -- Welz's `(1 + x)/((x - 2) sqrt(1 + x^3))` is one + // logarithm, and over `sqrt(1 + x) sqrt(1 - x + x^2)` it was three. if (ReferenceEquals(product.Node, factor.Node) || product.Base == factor.Base || product.Exponent is Number.Integer && factor.Exponent is Number.Integer + || factor.Exponent is Number.Integer { EInteger: var once } && once.Abs().CompareTo(EInteger.FromInt32(2)) < 0 || ExactlyDivided(product.Base, factor.Base, x) is not { } cofactor || !cofactor.ContainsNode(x)) continue; var multiple = Variable.CreateUnique(expr, "k_split"); From 8d62819cc2fc380eae63714a55834df331392040 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 14 Oct 2026 23:38:04 +0000 Subject: [PATCH 3/4] Split beside a whole power of a sum below the bar only Above the bar the split took the answer for (a + b x)^3 sqrt(c x^2 - a^2 c/b^2) from 3,540 characters to 33,316, and beside a power of x x^(5/2)/(b x^2 + c x^4)^3 from 1.7 seconds to 7.9. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index dc0b4f1d8..7b846215c 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -19351,8 +19351,8 @@ Entity TakeOut(Entity node) { if (!Integration.AnsweringTheQuestionAsked || splittingARootOfAProduct) return null; - var roots = new List<(Entity Node, Entity Base, Entity Exponent)>(); - Gather(expr); + var roots = new List<(Entity Node, Entity Base, Entity Exponent, bool Below)>(); + Gather(expr, false); if (roots.Count < 2) return null; foreach (var product in roots) @@ -19366,6 +19366,12 @@ Entity TakeOut(Entity node) if (ReferenceEquals(product.Node, factor.Node) || product.Base == factor.Base || product.Exponent is Number.Integer && factor.Exponent is Number.Integer || factor.Exponent is Number.Integer { EInteger: var once } && once.Abs().CompareTo(EInteger.FromInt32(2)) < 0 + // A whole power beside a root only where the split gathers powers below + // the bar, and of a sum: above it, `(a + b x)^3 sqrt(c x^2 - a^2 c/b^2)` went + // from 3,540 characters to 33,316, and `x^(5/2)/(b x^2 + c x^4)^3`, a power + // of x, from 1.7 seconds to 7.9. + || factor.Exponent is Number.Integer && !factor.Below + || product.Exponent is Number.Integer && factor.Base is not (Sumf or Minusf) || ExactlyDivided(product.Base, factor.Base, x) is not { } cofactor || !cofactor.ContainsNode(x)) continue; var multiple = Variable.CreateUnique(expr, "k_split"); @@ -19390,17 +19396,17 @@ Entity TakeOut(Entity node) } return null; - void Gather(Entity node) + void Gather(Entity node, bool below) { switch (node) { case Mulf(var left, var right): - Gather(left); - Gather(right); + Gather(left, below); + Gather(right, below); break; - case Divf(var above, var below): - Gather(above); - Gather(below); + case Divf(var above, var under): + Gather(above, below); + Gather(under, !below); break; // A symbolic exponent as well: K is exp(2 pi i n r) for a whole n constant // between the zeros of P and Q, whatever r is. Rubi's 1.2.1.2:2389, @@ -19408,17 +19414,17 @@ void Gather(Entity node) // `(d + e x)^(-3 - p) (a e + c d x)^p`. case Powf(var @base, var exponent) when exponent is not Number.Integer && !exponent.ContainsNode(x) && @base.ContainsNode(x) && (exponent is Number.Rational || exponent.Vars.Any()): - roots.Add((node, @base, exponent)); + roots.Add((node, @base, exponent, below)); break; // And a whole power, or a sum standing as a factor, beside a root: Rubi's // 1.2.1.2:2218, `1/((d + e x)^4 (a d e + (c d^2 + a e^2) x + c d e x^2)^(3/2))`, is // `(d + e x)^(-11/2) (a e + c d x)^(-3/2)` up to the constant, and :2280 has the // product to the first power below the bar beside `sqrt(d + e x)`. case Powf(var @base, Number.Integer { IsZero: false } whole) when @base is Sumf or Minusf && @base.ContainsNode(x): - roots.Add((node, @base, whole)); + roots.Add((node, @base, whole, below ^ whole.EInteger.Sign < 0)); break; case Sumf or Minusf when node.ContainsNode(x): - roots.Add((node, node, Number.Integer.One)); + roots.Add((node, node, Number.Integer.One, below)); break; } } From 773eae1d64b4da02e8b37c17f3914a522ea38105 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 15 Oct 2026 01:29:55 +0000 Subject: [PATCH 4/4] Record the answers a power of a product split beside a whole power gives Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 0c3b37ae9..5b3f0bb95 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -766,6 +766,24 @@ split too, and the powers of `P` gather ([#718](https://github.com/asc-community | `"(d + k*x)^m*(f + g*x)^3/(a*d*k + (c*d^2 + a*k^2)*x + c*d*k*x^2)^m".ToEntity().Integrate("x")` | `integral(...)` | 1,817 characters | | `"(1 - x^4)^n/(1 + x + x^2 + x^3)^n".ToEntity().Integrate("x")` | `integral(...)` | 147 characters | +### A power of a product beside a whole power of one of its factors below the bar is split + +**Answers where there were none.** `1/((d + k x)^4 (a d k + (c d^2 + a k^2) x + c d k x^2)^(3/2))` was declined +past any budget. The split of a root of a product beside a root of a factor now also takes a factor to a whole +power of two or more below the bar, and a product to a whole power beside a root of a factor, so the powers of +the factor gather. It is asked before the routes that read the product whole: `(d + k x)^(7/2)/(a d k + (c d^2 ++ a k^2) x + c d k x^2)` was answered by them in 29,285 characters +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/((d + k*x)^4*(a*d*k + (c*d^2 + a*k^2)*x + c*d*k*x^2)^(3/2))".ToEntity().Integrate("x")` | `integral(...)` | 663 characters | +| `"1/(sqrt(d + k*x)*(a*d*k + (c*d^2 + a*k^2)*x + c*d*k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 1,435 characters | +| `"(d + k*x)^(7/2)/(a*d*k + (c*d^2 + a*k^2)*x + c*d*k*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 1,611 characters | +| `"(A + B*x)*(a*c + b*c*x)^m/(a^2 + 2*a*b*x + b^2*x^2)^3".ToEntity().Integrate("x")` | `integral(...)` | 223 characters | +| `"(f + g*x)/((d + k*x)^3*(c*d^2 - b*d*k - b*k^2*x - c*k^2*x^2)^(5/2))".ToEntity().Integrate("x")` | `integral(...)` | 1,156 characters | +| `"(12 - 3*k^2*x^2)^(1/4)/(2 + k*x)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 613 characters | + ### A secant or a cosecant beside a power of a sum of them is read as a power of the cosine or the sine **Answers where there were none.** `sec(x) sqrt(sec(x) + tan(x))` was declined, with the rest of Rubi's 4.7.7