From f13576b074887c2c40a8e147440168c672d4b9af Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 16 Oct 2026 20:16:06 +0000 Subject: [PATCH 1/3] Take a power of x out of the bracket of the derivative of a product of powers A bracket that is a polynomial with a power of x in every term has that power taken out, to join the powers it multiplies: x^(2n) (c + d x)^n (2 c x + 3 d x^2) is x^(2n + 1) (c + d x)^n (2 c + 3 d x), the derivative of x^(2n + 2) (c + d x)^(n + 1)/(n + 1), and with the x in the bracket nothing raised x by enough. Rubi's 1.3.1 had it declined. Part of https://github.com/asc-community/AngouriMath/issues/718 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Integration/IndefiniteIntegralSolver.cs | 38 +++++++++++++ ...utOfTheBracketOfADerivativeIntegralTest.cs | 57 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/APowerOfXOutOfTheBracketOfADerivativeIntegralTest.cs diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 1ca87d71a..9d9b3e753 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -14618,6 +14618,38 @@ static bool More(Entity difference) return common * bracket; } + /// + /// with each factor above the bar that is a sum and a polynomial in + /// divisible by a power of it written as that power times the quotient, + /// rebuilt from its coefficients; null where no factor is. + /// + private static Entity? WithAPowerOfXOutOfTheBracket(Entity expr, Entity.Variable x) + { + var (top, bottom) = Functions.SingleQuotient.Of(expr); + var changed = false; + Entity rebuilt = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(top)) + { + if (factor is Sumf or Minusf && TreeAnalyzer.TryGetPolynomial(factor, x, out var read) && read.Count > 1 + && read.Values.All(value => !value.ContainsNode(x)) && read.Keys.All(key => key.Sign >= 0 && key.CanFitInInt32()) + && read.Keys.Min()! is { Sign: > 0 } lowest) + { + var shift = lowest.ToInt32Unchecked(); + Entity reduced = Number.Integer.Zero; + foreach (var (power, coefficient) in read) + { + var k = power.ToInt32Unchecked() - shift; + reduced += coefficient * (k == 0 ? Number.Integer.One : MathS.Pow(x, Number.Integer.Create(k))); + } + rebuilt *= MathS.Pow(x, Number.Integer.Create(shift)) * reduced; + changed = true; + } + else + rebuilt *= factor; + } + return changed ? rebuilt / bottom : null; + } + /// /// 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 @@ -14657,6 +14689,12 @@ static bool More(Entity difference) // elementary. if (expr is Sumf or Minusf) return WithTheCommonPowersOut(expr, x) is { } factored ? SolveAsTheDerivativeOfAProductOfPowers(factored, x) : null; + // A bracket that is a polynomial with a power of x in every term has that power taken out, to + // join the powers it multiplies: `x^(2n) (c + d x)^n (2 c x + 3 d x^2)` is + // `x^(2n + 1) (c + d x)^n (2 c + 3 d x)`, the derivative of `x^(2n + 2) (c + d x)^(n + 1)/(n + 1)`, + // and with the x in the bracket nothing raised x by enough. + if (WithAPowerOfXOutOfTheBracket(expr, x) is { } outOfTheBracket) + return SolveAsTheDerivativeOfAProductOfPowers(outOfTheBracket, x); Entity constant = Number.Integer.One; Entity? bracket = null; var raisable = new List<(Entity Base, Entity Exponent)>(); diff --git a/Sources/Tests/UnitTests/Calculus/APowerOfXOutOfTheBracketOfADerivativeIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerOfXOutOfTheBracketOfADerivativeIntegralTest.cs new file mode 100644 index 000000000..ebaf46fb2 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerOfXOutOfTheBracketOfADerivativeIntegralTest.cs @@ -0,0 +1,57 @@ +// +// 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 System; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// The derivative of a product of powers with a power of x in every term of its bracket: taken out, + /// it joins the power of x it multiplies. Rubi's 1.3.1 x^(2n) (c + d x)^n (2 c x + 3 d x^2). + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class APowerOfXOutOfTheBracketOfADerivativeIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + var pinned = integral.Substitute("C", 0); + Entity original = integrand.ToEntity(); + foreach (var (name, value) in pins) + { + pinned = pinned.Substitute(name, value); + original = original.Substitute(name, value); + } + var derivative = pinned.Differentiate("x"); + foreach (var at in new[] { -2.3, -0.7, 0.4, 1.9 }) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + var difference = Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart); + var scale = Math.Max(1.0, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)); + Assert.True(difference / scale < 1e-8, + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + } + + // d with the sign that keeps the argument real at every sampled x, and |d| below 2/2.3^2 + // so that the inverse sine and cosine stay inside [-1, 1]; off it the exact checks hold too. + [Theory] + [InlineData("x^(2*n)*(c+d*x)^n*(2*c*x+3*d*x^2)")] + [InlineData("x^(n-1)*(c+d*x^2)^n*(c*x+3*d*x^3)")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("c", 1.4), ("d", 0.75), ("n", 2.5)); + DifferentiatesBack(integrand, ("c", 0.5), ("d", -0.3), ("n", 1.0 / 3)); + } + } +} From 4dc6982b2b9da2d2278dc338dc2eeb34a81be23a Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 17 Oct 2026 01:34:57 +0000 Subject: [PATCH 2/3] Read the polynomial's monomials without deconstruction, which netstandard2.0 lacks Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- .../Continuous/Integration/IndefiniteIntegralSolver.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 7ff8c1631..745be6760 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -14636,10 +14636,10 @@ static bool More(Entity difference) { var shift = lowest.ToInt32Unchecked(); Entity reduced = Number.Integer.Zero; - foreach (var (power, coefficient) in read) + foreach (var monomial in read) { - var k = power.ToInt32Unchecked() - shift; - reduced += coefficient * (k == 0 ? Number.Integer.One : MathS.Pow(x, Number.Integer.Create(k))); + var k = monomial.Key.ToInt32Unchecked() - shift; + reduced += monomial.Value * (k == 0 ? Number.Integer.One : MathS.Pow(x, Number.Integer.Create(k))); } rebuilt *= MathS.Pow(x, Number.Integer.Create(shift)) * reduced; changed = true; From f62dfc71c413358673c5a41c82b1c57f87142de9 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 17 Oct 2026 01:35:54 +0000 Subject: [PATCH 3/3] Record the power of x taken out of a bracket in the breaking changes Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 69f8bbf7d..49af14dbf 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -103,6 +103,18 @@ since semantic versioning admits it. Measured on the suite: **7 of 8,525 tests** referenced the old reading — five boolean-solver inputs written `A | B`, and two that existed to record this spelling as at risk. +### A power of x is taken out of a bracket to find the derivative of a product of powers + +**Answers where there were none.** `x^(2n) (c + d x)^n (2 c x + 3 d x^2)`, Rubi's 1.3.1, was declined. A bracket that +is a polynomial with a power of x in every term now has that power taken out to join the powers it multiplies, and the +integrand is then `x^(2n + 1) (c + d x)^n (2 c + 3 d x)`, the derivative of `x^(2n + 2) (c + d x)^(n + 1)/(n + 1)` +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x^(2*n)*(c+d*x)^n*(2*c*x+3*d*x^2)".ToEntity().Integrate("x")` | `integral(...)` | 61 characters | +| `"x^(n-1)*(c+d*x^2)^n*(c*x+3*d*x^3)".ToEntity().Integrate("x")` | `integral(...)` | 65 characters | + ### The last digits of an evaluated sine, cosine, tangent, arcsine or arctangent **Silent, and in the hundredth digit.** `EvalNumerical` at the default hundred digits gives the