From f93f77936c1f4905448afcb62cc7e6d315bbfc5f Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 5 Oct 2026 13:06:11 +0000 Subject: [PATCH] A sum that is the derivative of a product of powers is read as one m x^(m - 1) (a + b x^n)^p + b n p x^(m + n - 1) (a + b x^n)^(p - 1) is the derivative of x^m (a + b x^n)^p written out as a sum, and was declined: the rule that reads the derivative of a product of powers reads the product beside one bracket, and split into its terms neither term is elementary. A sum of products of powers of the same bases is written as their common product, each base to the least of its exponents, times the bracket of what is left, and asked so; the differences of the exponents are simplified, since -1 + m + n - (-1 + m) is left as written by the inner simplification. Rubi's 1.1.3.2. Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 14 ++++ .../Integration/IndefiniteIntegralSolver.cs | 84 +++++++++++++++++++ .../Calculus/ProductOfPowersDerivativeTest.cs | 11 +++ 3 files changed, 109 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 3d1e03d67..476523f7b 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -369,6 +369,20 @@ is written so, after the substitutions, and the factor goes in front of the answ | `"(1/(1 - x^2))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | `sgn(1 - x^2) x/sqrt(1 - x^2)` | | `"(c/(a + b*x^2))^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | `(c/(a + b x^2))^(3/2) (a + b x^2)^(3/2) x/(a sqrt(a + b x^2))` | +### A sum that is the derivative of a product of powers is read as one + +**Answers where there were none.** `m x^(m - 1) (a + b x^n)^p + b n p x^(m + n - 1) (a + b x^n)^(p - 1)` is +the derivative of `x^m (a + b x^n)^p`, written out as a sum, and was declined: the rule that reads a +derivative of a product of powers reads the product beside one bracket, and split into its terms neither is +elementary. A sum of products of powers of the same bases is written as their common product, each base +to the least of its exponents, times the bracket of what is left, and asked so. Rubi's 1.1.3.2 +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"m*x^(m-1)*(a+b*x^n)^p + b*n*p*x^(m+n-1)*(a+b*x^n)^(p-1)".ToEntity().Integrate("x")` | `integral(...)` | `x^m (a + b x^n)^p` | +| `"-1/2*b*n*x^(-1+m+n)/(a+b*x^n)^(3/2)+m*x^(-1+m)/sqrt(a+b*x^n)".ToEntity().Integrate("x")` | `integral(...)` | `x^m/sqrt(a + b x^n)` | + ### A whole power of a quotient with a symbol in it is integrated as the quotient of the powers **Answers where there were none, and a slowdown since 2.5.0 undone.** `(c/(a + c x^2))^2` was diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 2e96723dc..adf2edb00 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -12827,6 +12827,83 @@ static bool IsRationalInBoth(Entity e, Entity.Variable x, Entity.Variable L) /// The largest power of the logarithm the tower ansatz reads or tries. private const int MaximumTowerDegree = 4; + /// + /// , every term of which is a constant times powers of the same + /// bases with in them, as the product of those bases to the least of + /// each one's exponents times the sum of what is left. Null where a base is not in every + /// term, where no exponent of one is the least, or where the least are all whole, which + /// the rational rules read. + /// + private static Entity? WithTheCommonPowersOut(Entity sum, Entity.Variable x) + { + var terms = new List<(Entity Constant, Dictionary Powers)>(); + foreach (var term in Sumf.LinearChildren(sum)) + { + Entity constant = Number.Integer.One; + var powers = new Dictionary(); + foreach (var (factor, underneath) in FactorsOfTheIntegrand(term)) + { + if (!factor.ContainsNode(x)) + { + constant = underneath ? constant / factor : constant * factor; + continue; + } + var (@base, exponent) = factor is Powf(var b, var e) && !e.ContainsNode(x) ? (b, e) : (factor, (Entity)Number.Integer.One); + if (@base is Number || !@base.ContainsNode(x)) + return null; + if (underneath) + exponent = -exponent; + powers[@base] = powers.TryGetValue(@base, out var before) ? (before + exponent).InnerSimplified : exponent.InnerSimplified; + } + terms.Add((constant, powers)); + } + if (terms.Count < 2) + return null; + var bases = terms[0].Powers.Keys.ToList(); + if (bases.Count == 0 || terms.Any(term => term.Powers.Count != bases.Count || bases.Any(@base => !term.Powers.ContainsKey(@base)))) + return null; + // For each base the least of its exponents, against which every other is more by a + // whole number or by a sum of symbols with no sign in front: `x^(m - 1)` beside + // `x^(m + n - 1)` leaves `x^n` in the bracket, as the derivative of `a + b x^n` does. + // Simplified, not inner-simplified: `-1 + m + n - (-1 + m)` is left as written by the + // latter, and is `n`. + static Entity Difference(Entity exponent, Entity from) + => Functions.PartialFractions.Bare((exponent - from).Simplify()); + static bool More(Entity difference) + => difference is Number.Integer { EInteger.Sign: >= 0 } + || difference is not Number && difference is not Mulf(Number.Real { IsNegative: true }, _) && difference.Vars.Any(); + var least = new Dictionary(); + foreach (var @base in bases) + { + var exponents = terms.Select(term => term.Powers[@base]).ToList(); + Entity? lowest = null; + foreach (var candidate in exponents) + if (exponents.All(other => Difference(other, candidate) is var difference && (difference == Number.Integer.Zero || More(difference)))) + { + lowest = candidate; + break; + } + if (lowest is null) + return null; + least[@base] = lowest; + } + if (least.Values.All(exponent => exponent is Number.Integer)) + return null; + Entity bracket = Number.Integer.Zero; + foreach (var (constant, powers) in terms) + { + Entity rest = constant; + foreach (var @base in bases) + if (Difference(powers[@base], least[@base]) is var raised && raised != Number.Integer.Zero) + rest = rest * (raised == Number.Integer.One ? @base : MathS.Pow(@base, raised)); + bracket = bracket == Number.Integer.Zero ? rest : bracket + rest; + } + Entity common = Number.Integer.One; + foreach (var @base in bases) + common = common * MathS.Pow(@base, least[@base]); + return common * bracket; + } + /// /// A product of powers times a sum that is the derivative of the product with some of /// the powers raised by one: e^x x^2 ln(x)^2 (3 + (3 + x) ln(x)) is @@ -12859,6 +12936,13 @@ static bool IsRationalInBoth(Entity e, Entity.Variable x, Entity.Variable L) { if (!Integration.AnsweringTheQuestionAskedOrOneBelow) return null; + // A sum of products of powers of the same bases is their common product times a + // bracket, which is the shape read below: `m x^(m - 1) (a + b x^n)^p + b n p x^(m + n - 1) + // (a + b x^n)^(p - 1)` is `x^(m - 1) (a + b x^n)^(p - 1) (m (a + b x^n) + b n p x^n)`, + // the derivative of `x^m (a + b x^n)^p`. As a sum it was split, and neither term is + // elementary. + if (expr is Sumf or Minusf) + return WithTheCommonPowersOut(expr, x) is { } factored ? SolveAsTheDerivativeOfAProductOfPowers(factored, x) : null; Entity constant = Number.Integer.One; Entity? bracket = null; var raisable = new List<(Entity Base, Entity Exponent)>(); diff --git a/Sources/Tests/UnitTests/Calculus/ProductOfPowersDerivativeTest.cs b/Sources/Tests/UnitTests/Calculus/ProductOfPowersDerivativeTest.cs index 31e9dfa9f..425e3cb77 100644 --- a/Sources/Tests/UnitTests/Calculus/ProductOfPowersDerivativeTest.cs +++ b/Sources/Tests/UnitTests/Calculus/ProductOfPowersDerivativeTest.cs @@ -125,6 +125,17 @@ public void TwoSymbolicPowersOfPolynomials() public void WithoutASumOrWithXRaisedFromNothing(string integrand) => DifferentiatesBackWithParametersPinned(integrand, Points); + /// + /// The derivative written out as a sum, the common powers in each term: they come out in + /// front and the rest is the bracket, where split into its terms neither is elementary. + /// Rubi's 1.1.3.2. + /// + [Theory] + [InlineData("-1/2*b*n*x^(-1+m+n)/(a+b*x^n)^(3/2)+m*x^(-1+m)/sqrt(a+b*x^n)")] + [InlineData("m*x^(m-1)*(a+b*x^n)^p + b*n*p*x^(m+n-1)*(a+b*x^n)^(p-1)")] + public void ASumWithTheCommonPowersInEachTerm(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()