diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index abfa92ca4..1a185e7c0 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -1983,6 +1983,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
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 031d3595f..094b4ef96 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -9055,6 +9055,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)
@@ -9095,34 +9100,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());
}
}