From cad5b2e575c8ef93117fe8559f0a18fccff9a36f Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 19 Oct 2026 22:41:19 +0000 Subject: [PATCH] Integrate a power of a sum beside its derivative with symbolic powers in it c g' g^k is c g^(k + 1)/(k + 1), and c ln(g) for k = -1, but the derivative of ln(c x^n)^q is written q ln(c x^n)^(q - 1) c n x^(n - 1)/(c x^n), which nothing brought down to the n q ln(c x^n)^(q - 1)/x the integrand writes. The terms are now read as a coefficient times powers of x and of the other functions of x in them, each base's powers merged, and a power of x common to the sum's terms is taken out of it: a x^m + b x ln(c x^n)^q is x (a x^(m - 1) + b ln(c x^n)^q). Rubi's 3.5: rows 29, 31, 38, 39 and 40 go from unevaluated or timed out to solved (family 3 1349 -> 1354 of 1382), 0 wrong. Families 0 and 1 and samples of 2-8 otherwise identical; gate and suite pass. Part of #233. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 14 ++ .../Integration/IndefiniteIntegralSolver.cs | 155 ++++++++++++++++++ .../Integration/Integration.Definition.cs | 2 + ...erivativeWithSymbolicPowersIntegralTest.cs | 30 ++++ 4 files changed, 201 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/APowerOfASumBesideItsDerivativeWithSymbolicPowersIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 59087d3a0..5ec2f6700 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -203,6 +203,20 @@ compared everywhere with its value at one positive point | `"asin(sqrt(1+b*x^2))^n/sqrt(1+b*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 136 characters | | `"acos(sqrt(1+b*x^2))^n/sqrt(1+b*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 137 characters | +### A power of a sum beside its derivative is read with the logarithms in it as factors + +**Answers where there were none.** `(a m x^m + b n q ln(c x^n)^(q - 1))/(x (a x^m + b ln(c x^n)^q))` was declined, and so +were five more of Rubi's 3.5, the sum's square and the powers of `a x^2 + b x ln(c x^n)^2` among them. Each is a +constant times `g' g^k` for a sum `g`, but the derivative of `ln(c x^n)^q` is written `q ln(c x^n)^(q - 1) c n x^(n - 1)/(c x^n)`, +which nothing brought down to the `n q ln(c x^n)^(q - 1)/x` the integrand writes. The terms are now read as a +coefficient times powers of x and of the other functions of x in them, and a power of x common to the sum is taken +out of it ([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(a*m*x^m+b*n*q*ln(c*x^n)^(q-1))/(x*(a*x^m+b*ln(c*x^n)^q))".ToEntity().Integrate("x")` | `integral(...)` | `ln(a x^m + b ln(c x^n)^q)` | +| `"(a*(m-1)*x^(m-1)+b*n*q*ln(c*x^n)^(q-1))/(a*x^m+b*x*ln(c*x^n)^q)".ToEntity().Integrate("x")` | `integral(...)` | `ln(a x^(m - 1) + b ln(c x^n)^q)` | + ### A rational evaluated at a higher precision than before has all of its digits **More digits where there were a hundred.** A rational kept its decimal value in the precision current when it was diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index c372e4b2b..6986f050e 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -34500,6 +34500,161 @@ Entity Named(Entity value) } } + /// + /// A power of a sum beside a constant multiple of the sum's derivative, with symbolic powers + /// about: c g' g^k is c g^(k + 1)/(k + 1), and c ln(g) for k = -1. + /// Each term is read as a coefficient times a power of x times powers of the other + /// functions of x in it, the powers of each merged, so that + /// d/dx ln(c x^n)^q = q n ln(c x^n)^(q - 1)/x is matched against + /// b n q ln(c x^n)^(q - 1)/x as written. Where the terms do not match, a power of + /// x common to every term of the sum is taken out of it into the rest: + /// a x^m + b x ln(c x^n)^q is x (a x^(m - 1) + b ln(c x^n)^q). Rubi's 3.5. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + /// + /// reads only powers of + /// x, and the substitution's simplification of the quotient by du/dx never + /// brings c n x^(n - 1)/(c x^n) down to n/x. Only where a power is symbolic: + /// otherwise the substitution answers it. + /// + internal static Entity? SolveAPowerOfASumBesideItsDerivativeWithSymbolicPowers(Entity expr, Entity.Variable x) + { + if (!expr.Nodes.Any(node => node is Powf(var @base, var exponent) && @base.ContainsNode(x) && !exponent.ContainsNode(x) && exponent.Vars.Any())) + return null; + var factors = FactorsOfTheIntegrand(expr); + for (var chosen = 0; chosen < factors.Count; chosen++) + { + var (factor, underneath) = factors[chosen]; + var (sum, power) = factor is Powf(var b, var e) && !e.ContainsNode(x) ? (b, e) : (factor, (Entity)Number.Integer.One); + if (sum is not (Sumf or Minusf) || !sum.ContainsNode(x) || power.Evaled is not Number.Rational k) + continue; + if (underneath) + k = (Number.Rational)(-k); + // The rest: at most one sum above the bar, every other factor one term. + Entity? restSum = null; + Entity monomial = Number.Integer.One; + var readable = true; + for (var other = 0; other < factors.Count && readable; other++) + { + if (other == chosen) + continue; + var (f, below) = factors[other]; + if (!below && restSum is null && f is Sumf or Minusf && f.ContainsNode(x)) + restSum = f; + else if (f is Sumf or Minusf && f.ContainsNode(x)) + readable = false; + else + monomial = below ? monomial / f : monomial * f; + } + if (!readable) + continue; + var rest = (restSum is null ? new[] { monomial } : Sumf.LinearChildren(restSum).Select(term => term * monomial)).ToList(); + if (Matched(rest, sum, x) is { } constant) + return Answer(constant, sum, k); + // A power of x common to the sum's terms, taken out: x^j g, so that c x^(j k) g'... is read against g. + if (Terms(Sumf.LinearChildren(sum), x) is not { } terms) + continue; + foreach (var common in terms.Select(t => t.Exponent).Where(e => e.Evaled is Number.Integer { IsZero: false }).Distinct().ToList()) + { + Entity reduced = Number.Integer.Zero; + foreach (var t in terms) + { + var exponent = (t.Exponent - common).InnerSimplified; + Entity written = t.Atoms.Aggregate(t.Coefficient, (product, atom) => product * MathS.Pow(atom.Base, atom.Power)); + if (exponent.Evaled is not Number.Complex { IsZero: true }) + written *= exponent == Number.Integer.One ? x : MathS.Pow(x, exponent); + reduced = reduced == Number.Integer.Zero ? written : reduced + written; + } + // x^(common k) moves into the rest. + var moved = MathS.Pow(x, (common * k).InnerSimplified); + if (Matched(rest.Select(term => term * moved).ToList(), reduced, x) is { } c) + return Answer(c, reduced, k); + } + } + return null; + + static Entity Answer(Entity constant, Entity sum, Number.Rational k) + => k == Number.Integer.MinusOne + ? constant * MathS.Ln(sum) + : constant / (k + 1) * MathS.Pow(sum, (Entity)(k + 1)); + + // The constant c with the rest's terms c times the sum's derivative's, or null. + static Entity? Matched(List rest, Entity sum, Entity.Variable x) + { + var derivative = sum.Differentiate(x); + if (derivative is Providedf(var bare, _)) + derivative = bare; + if (Terms(rest, x) is not { } above || Terms(Sumf.LinearChildren(derivative), x) is not { } below || above.Count != below.Count) + return null; + Entity? constant = null; + foreach (var term in above) + { + var matching = below.Where(other => SameMonomial(term, other)).ToList(); + if (matching.Count != 1) + return null; + var ratio = Bare((term.Coefficient / matching[0].Coefficient).Simplify()); + if (ratio.ContainsNode(x) || ratio.Nodes.Any(node => node == MathS.NaN)) + return null; + if (constant is null) + constant = ratio; + else if (Bare((constant - ratio).Simplify()) != Number.Integer.Zero) + return null; + } + return constant; + } + + static bool SameMonomial((Entity Coefficient, Entity Exponent, List<(Entity Base, Entity Power)> Atoms) one, + (Entity Coefficient, Entity Exponent, List<(Entity Base, Entity Power)> Atoms) other) + => Bare((one.Exponent - other.Exponent).Simplify()) == Number.Integer.Zero + && one.Atoms.Count == other.Atoms.Count + && one.Atoms.All(atom => other.Atoms.Any(theirs => theirs.Base == atom.Base && Bare((theirs.Power - atom.Power).Simplify()) == Number.Integer.Zero)); + + static Entity Bare(Entity simplified) => simplified is Providedf(var inner, _) ? inner : simplified; + + // Each term as a coefficient free of x, a power of x, and powers of other functions of + // x, each base's powers merged; null where a factor is a sum holding x. + static List<(Entity Coefficient, Entity Exponent, List<(Entity Base, Entity Power)> Atoms)>? Terms(IEnumerable terms, Entity.Variable x) + { + var read = new List<(Entity, Entity, List<(Entity, Entity)>)>(); + foreach (var term in terms) + { + var (numerator, denominator) = Functions.SingleQuotient.Of(term is Providedf(var t, _) ? t : term); + Entity exponent = Number.Integer.Zero; + Entity coefficient = Number.Integer.One; + var atoms = new List<(Entity Base, Entity Power)>(); + foreach (var (side, underneath) in new[] { (numerator, false), (denominator, true) }) + foreach (var factor in Mulf.LinearChildren(side)) + { + if (!factor.ContainsNode(x)) + { + coefficient = underneath ? coefficient / factor : coefficient * factor; + continue; + } + var (@base, power) = factor is Powf(var b, var e) && !e.ContainsNode(x) ? (b, e) : (factor, (Entity)Number.Integer.One); + if (underneath) + power = -power; + if (@base == x) + exponent += power; + else if (@base is Sumf or Minusf) + return null; + else + { + var at = atoms.FindIndex(atom => atom.Base == @base); + if (at < 0) + atoms.Add((@base, power)); + else + atoms[at] = (@base, atoms[at].Power + power); + } + } + // A term the derivative writes as 0 times something is no term. + if (coefficient.InnerSimplified.Evaled is Number.Complex { IsZero: true }) + continue; + read.Add((coefficient, exponent.InnerSimplified, atoms.Select(atom => (atom.Base, atom.Power.InnerSimplified)).ToList())); + } + return read; + } + } + /// /// An exponential of a polynomial beside the polynomial's derivative, with the rest a /// function of the polynomial: G^P k P' f(P) is k G^u f(u) under u = P. diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 3ceb0c6f3..2f5e7b9b9 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -1100,6 +1100,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // A logarithmic derivative the substitution above could not read for a symbol in // an exponent: `(x^(n-1) - 1)/(x^n - n x)`. if ((answer = IndefiniteIntegralSolver.SolveALogarithmicDerivativeWithASymbolicExponent(expr, x)) is { }) return answer; + // And a power of a sum beside its derivative, with functions of x besides powers of it in the terms. + if ((answer = IndefiniteIntegralSolver.SolveAPowerOfASumBesideItsDerivativeWithSymbolicPowers(expr, x)) is { }) return answer; // After the general substitution rather than inside it, because the general one // divides by du/dx and asks what is left, and that question loses the shape here: // sqrt(tan(x)) over the derivative of sqrt(tan(x)) simplifies to sin(2x), in which diff --git a/Sources/Tests/UnitTests/Calculus/APowerOfASumBesideItsDerivativeWithSymbolicPowersIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerOfASumBesideItsDerivativeWithSymbolicPowersIntegralTest.cs new file mode 100644 index 000000000..9cd089057 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerOfASumBesideItsDerivativeWithSymbolicPowersIntegralTest.cs @@ -0,0 +1,30 @@ +// +// 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 AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// A power of a sum beside its derivative, where the terms hold a symbolic power of a + /// logarithm: the derivative of ln(c x^n)^q is written q ln(c x^n)^(q - 1) c n x^(n - 1)/(c x^n), + /// which nothing brought down to the n q ln(c x^n)^(q - 1)/x the integrand writes. They were + /// declined. Rubi's 3.5. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class APowerOfASumBesideItsDerivativeWithSymbolicPowersIntegralTest + { + [Theory] + [InlineData("(a*m*x^m + b*n*q*ln(c*x^n)^(q - 1))*(a*x^m + b*ln(c*x^n)^q)/x", "1/2*(a*x^m + b*ln(c*x^n)^q)^2")] + [InlineData("(a*m*x^m + b*n*q*ln(c*x^n)^(q - 1))/(x*(a*x^m + b*ln(c*x^n)^q))", "ln(a*x^m + b*ln(c*x^n)^q)")] + [InlineData("(a*(m - 1)*x^(m - 1) + b*n*q*ln(c*x^n)^(q - 1))/(a*x^m + b*x*ln(c*x^n)^q)", "ln(a*x^(m - 1) + b*ln(c*x^n)^q)")] + public void TheSumToTheNextPower(string integrand, string expected) + => Assert.Equal(expected.ToEntity().InnerSimplified, integrand.ToEntity().Integrate("x").Substitute("C", 0).InnerSimplified); + } +}