diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 3547f0dd3..bdb013370 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -384,6 +384,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 31ec42351..2f1ae9b84 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()