From cac4911a010fb0138c58bd77e3f61c3f4c1494e7 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 2 Oct 2026 07:03:09 +0000 Subject: [PATCH 1/2] A product of powers is read as its own raised product's derivative without a sum beside it SolveAsTheDerivativeOfAProductOfPowers reads an integrand as a constant times the derivative of its powers with some of them raised by one. Three changes: - The derivative's sum is built as a sum of products, each raised base's derivative times the other raised bases, so that it reads as a polynomial and the constant comes off one monomial. Built with f'/f in it, it read as nothing, and the constant fell to simplifying a quotient of the two sums: past a minute for Rubi's 1.3.2 x (a + b x + c x^2)^m (d + e x + f x^2 + g x^3)^n (...), 0.3 s now. - Where a power is not a whole one, a product with no sum beside it is tried as well: 1/(x^2 sqrt(a x + b x^4)) is (-2/(3 a) sqrt(a x + b x^4)/x^2)'. - And there, x is raised from nothing where it is not among the powers: (a + b x^n + c x^(2 n))^p (a + b (1 + n + n p) x^n + ...) is (x (a + b x^n + c x^(2 n))^(p + 1))'. Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 61 +++++++++++++++---- .../Calculus/ProductOfPowersDerivativeTest.cs | 25 ++++++++ 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index c56b465f2..5f767cdee 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -9037,6 +9037,11 @@ static bool IsRationalInBoth(Entity e, Entity.Variable x, Entity.Variable L) /// ansatz wants whole powers of its monomials, and splitting the sum loses it, since /// F^(c(a + bx)) x^m ln(dx)^n on its own is not elementary. /// + /// + /// Where a power is not a whole one, the sum may be missing -- the product of powers is + /// then a constant times its own raised product's derivative -- and x may be raised from + /// nothing, a power of it the integrand does not have. + /// /// https://github.com/asc-community/AngouriMath/issues/718 /// internal static Entity? SolveAsTheDerivativeOfAProductOfPowers(Entity expr, Entity.Variable x) @@ -9077,34 +9082,68 @@ static bool IsRationalInBoth(Entity e, Entity.Variable x, Entity.Variable L) } raisable.Add((factor, underneath ? Number.Integer.MinusOne : Number.Integer.One)); } - if (bracket is null || raisable.Count == 0 || raisable.Count > 4) + if (raisable.Count == 0 || raisable.Count > 4) + return null; + // Where a power is not a whole one -- a root, or a symbolic exponent -- two more + // candidates, each a shape nothing else reads. With no sum beside two or more powers, + // the product is the derivative's up to a constant: `1/(x^2 sqrt(a x + b x^4))` is + // `(-2/(3 a) sqrt(a x + b x^4)/x^2)'`, the root raised and the power of x kept; a + // power on its own is the power rule's, or the binomial's. And x raised from nothing + // where it is not among the powers, in front of them: Rubi's + // `(a + b x^n + c x^(2 n))^p (a + b (1 + n + n p) x^n + c (1 + 2 n (1 + p)) x^(2 n))` + // is `(x (a + b x^n + c x^(2 n))^(p + 1))'`. Whole powers only are a rational + // function's, or a polynomial's beside a function, and the rules for those answer them. + if (raisable.Any(pair => pair.Exponent is not Number.Integer)) + { + if (raisable.Count >= 2) + bracket ??= Number.Integer.One; + if (!raisable.Any(pair => pair.Base == x)) + raisable.Insert(0, (x, Number.Integer.Zero)); + } + if (bracket is null) return null; for (var subset = 1; subset < 1 << raisable.Count; subset++) { Entity candidate = kept; Entity productOfRaisedBases = Number.Integer.One; - Entity logarithmicDerivative = Number.Integer.Zero; + for (var i = 0; i < raisable.Count; i++) + if ((subset & (1 << i)) != 0) + productOfRaisedBases *= raisable[i].Base; + // The bracket the candidate's derivative has, against the integrand's: the + // candidate is the integrand's powers with the raised bases in besides, so + // G' is (integrand without its bracket) (product of raised bases) (sum of the + // logarithmic derivatives), and the constant is the quotient of the brackets. + // Written as a sum of products, each raised base's logarithmic derivative times + // the other raised bases, so that it reads as a polynomial wherever the bases do: + // with `f'/f` left in, it read as nothing, and the constant fell to simplifying a + // quotient of the two brackets -- past a minute for Rubi's + // `x (a + b x + c x^2)^m (d + e x + f x^2 + g x^3)^n (...)`, 0.3 s read off one + // monomial. + Entity bracketOfTheDerivative = Number.Integer.Zero; foreach (var derivative in logarithmicDerivativesKept) - logarithmicDerivative += derivative; + bracketOfTheDerivative += derivative * productOfRaisedBases; for (var i = 0; i < raisable.Count; i++) { var (@base, exponent) = raisable[i]; var raised = (subset & (1 << i)) != 0; var newExponent = raised ? (exponent + 1).InnerSimplified : exponent; - if (raised) - productOfRaisedBases *= @base; if (newExponent.Evaled is Number.Complex { IsZero: true }) continue; // a power raised to nothing: not a factor of the candidate candidate *= newExponent == Number.Integer.One ? @base : MathS.Pow(@base, newExponent); - logarithmicDerivative += newExponent * @base.Differentiate(x) / @base; + if (!raised) + { + bracketOfTheDerivative += newExponent * @base.Differentiate(x) * productOfRaisedBases / @base; + continue; + } + Entity theOtherRaisedBases = Number.Integer.One; + for (var j = 0; j < raisable.Count; j++) + if (j != i && (subset & (1 << j)) != 0) + theOtherRaisedBases *= raisable[j].Base; + bracketOfTheDerivative += newExponent * @base.Differentiate(x) * theOtherRaisedBases; } if (candidate == kept) continue; - // The bracket the candidate's derivative has, against the integrand's: the - // candidate is the integrand's powers with the raised bases in besides, so - // G' is (integrand without its bracket) (product of raised bases) (sum of the - // logarithmic derivatives), and the constant is the quotient of the brackets. - var bracketOfTheDerivative = Functions.PartialFractions.Bare((productOfRaisedBases * logarithmicDerivative).InnerSimplified); + bracketOfTheDerivative = Functions.PartialFractions.Bare(bracketOfTheDerivative.InnerSimplified); if (!AreProportionalAtSampledPoints(bracket, bracketOfTheDerivative, x)) continue; // The constant is the quotient of one monomial's coefficients, the two diff --git a/Sources/Tests/UnitTests/Calculus/ProductOfPowersDerivativeTest.cs b/Sources/Tests/UnitTests/Calculus/ProductOfPowersDerivativeTest.cs index 7fc6e0048..31e9dfa9f 100644 --- a/Sources/Tests/UnitTests/Calculus/ProductOfPowersDerivativeTest.cs +++ b/Sources/Tests/UnitTests/Calculus/ProductOfPowersDerivativeTest.cs @@ -104,5 +104,30 @@ public void TheConstantIsRead() [Fact] public void WhatIsNotADerivativeIsDeclined() => Assert.Contains("integral(", "x^m*ln(x)^n*(1 + ln(x))".ToEntity().Integrate("x").Stringize()); + + /// + /// Rubi's 1.3.2, two symbolic powers of a quadratic and a cubic beside a power of x: + /// the sum is read as a polynomial and the constant off one monomial. It was a timeout. + /// + [Fact] + public void TwoSymbolicPowersOfPolynomials() + => DifferentiatesBackWithParametersPinned( + "x*(a+b*x+c*x^2)^m*(d+h*x+f*x^2+g*x^3)^n*(2*a*d+(3*b*d+3*a*h+b*d*m+a*h*n)*x+(4*c*d+4*b*h+4*a*f+2*c*d*m+b*h*m+b*h*n+2*a*f*n)*x^2+(5*c*h+5*b*f+5*a*g+2*c*h*m+b*f*m+c*h*n+2*b*f*n+3*a*g*n)*x^3+(6*c*f+6*b*g+2*c*f*m+b*g*m+2*c*f*n+3*b*g*n)*x^4+c*g*(7+2*m+3*n)*x^5)", + Points); + + /// + /// Where a power is not a whole one, a product with no sum beside it, Rubi's 1.1.4.2, and + /// x raised from a power the integrand does not have, Rubi's 1.2.3.5. + /// + [Theory] + [InlineData("1/(x^2*sqrt(a*x+b*x^4))")] + [InlineData("(a+b*x^n+c*x^(2*n))^p*(a+b*(1+n+n*p)*x^n+c*(1+2*n*(1+p))*x^(2*n))")] + public void WithoutASumOrWithXRaisedFromNothing(string integrand) + => DifferentiatesBackWithParametersPinned(integrand, Points); + + /// A product of powers that is no such derivative is still declined: sqrt(1 + x^3) is elliptic. + [Fact] + public void APowerThatIsNoDerivativeIsDeclined() + => Assert.Contains("integral(", "sqrt(1 + x^3)".ToEntity().Integrate("x").Stringize()); } } From ebf1ec5485123be72cd3d061bde5bff1aadfda76 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 2 Oct 2026 07:54:58 +0000 Subject: [PATCH 2/2] A breaking-changes entry for a product of powers read without a sum beside it Measured on the 2.5.0 tag and on this branch. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index b5268044d..d4e3a893d 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1844,6 +1844,21 @@ and in no release -- and is `sgn(u) u/(u^2 - 1)` | `"1/sqrt(1+csch(x)^2)".Integrate("x")` | left unevaluated | `-sgn(tanh(x)) ln(tanh(x)^2 - 1)/2`, in exponentials | | `"x^4/sech(2*ln(c*x))^(1/2)".Integrate("x")` | left unevaluated | `sgn(x) (1 + c^4 x^4)^(3/2)/(6 sqrt(2) c^5)` up to the form, `provided c^4 > 0` | +### A product of powers is read as its own raised product's derivative without a sum beside it + +**Improvement, not silent.** An integrand that is a constant times the derivative of its powers +with some of them raised by one is answered by that product. The rule wanted a sum beside the powers +and a power of `x` among them, and read the sum's quotient through `f'/f`, which with two symbolic +powers of polynomials ran past a minute. Where a power is not a whole one it now reads a product +with no sum, and `x` raised from no power at all, and the sum as a polynomial +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/(x^2*sqrt(a*x+b*x^4))".Integrate("x")` | left unevaluated | `-2 sqrt(a x + b x^4)/(3 a x^2)` | +| `"(a+b*x^n+c*x^(2*n))^p*(a+b*(1+n+n*p)*x^n+c*(1+2*n*(1+p))*x^(2*n))".Integrate("x")`, Rubi's 1.2.3.5 | no answer in 30 s | `x (a + b x^n + c x^(2 n))^(p + 1)` | +| Rubi 1.3.2's `x (a + b x + c x^2)^m (d + e x + f x^2 + g x^3)^n` times the sum its derivative leaves | no answer in 90 s | `x^2 (a + b x + c x^2)^(m + 1) (d + e x + f x^2 + g x^3)^(n + 1)` | + ### A root of a rational function of `coth(x)` is integrated by the hyperbolic half-angle `coth(x)^3/sqrt(a + b coth(x)^2)` was a search past the budget. The rational-in-`tanh` route