From 36ed0d63b3427ba7ba490534b6d2465594868307 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sat, 10 Oct 2026 04:45:04 +0000 Subject: [PATCH] An odd power of a quadratic's derivative over a power of it, in the quadratic 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 | 70 +++++++++++++++++++ ...owerOfAQuadraticsDerivativeIntegralTest.cs | 50 +++++++++++++ 3 files changed, 134 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/AnOddPowerOfAQuadraticsDerivativeIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index fc26529d2..1605e376d 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -193,6 +193,20 @@ of a linear over a linear, which is answered in a second | `"1/(sqrt(a + i*a*tan(x))*(c + d*tan(x))^(3/2))".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 2,217 characters | | `"1/((a + i*a*tan(x))^(3/2)*(c + d*tan(x))^(5/2))".ToEntity().Integrate("x")` | `integral(...)`; past thirty seconds on the unreleased master | 3,333 characters | +### An odd power of a quadratic's derivative over a power of the quadratic is integrated in the quadratic + +**Shorter answers, and answers where there were none.** `(b + 2c x)^9/(a + b x + c x^2)^3` was answered through the +partial fractions in 76,153 characters, with a case for each sign of the discriminant. In `u = a + b x + c x^2` an +odd power of a multiple of the derivative is a polynomial in `u` over a power of it, since +`(b + 2c x)^2 = 4c u + b^2 - 4ac`, and the answer is a few powers of the quadratic. The same with a symbol shared +by the linear's coefficients, `(b d + 2c d x)^9`, which ran past a minute +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(b + 2*c*x)^9/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute; 76,153 characters on the unreleased master | 607 characters | +| `"(b*d + 2*c*d*x)^9/(a + b*x + c*x^2)^3".ToEntity().Integrate("x")` | past a minute | 611 characters | + ### A symbolic quadratic with a square discriminant is split into its linears before the division **Answers where there were none.** `(d + e x)^8/(a d e + (c d^2 + a e^2) x + c d e x^2)^2` ran past a minute, with diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 76c5c9b42..1722eb66c 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -656,6 +656,70 @@ Entity Back(Entity mapped) return (cancelled ? Back(above.ToEntity(variables)) : numerator, factoredBelow); } + /// + /// K L^m/Q^n for a quadratic Q = A x^2 + B x + C, a linear L a multiple + /// lambda of its derivative and an odd m >= 3, in u = Q: L^m dx is + /// lambda^m (Q')^(m - 1) du, and (Q')^2 = 4A u + B^2 - 4AC, so the integrand is + /// K lambda^m (4A u + B^2 - 4AC)^((m - 1)/2)/u^n, a polynomial over a power of u. + /// for anything else. + /// + /// + /// Rubi's 1.2.1.2 has (b d + 2c d x)^9/(a + b x + c x^2)^3; through the partial fractions + /// its answer was seventy-six thousand characters, with a case for each sign of the + /// discriminant, where in u it is a sum of a few powers. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + private static Entity? InTheQuadraticAnOddPowerOfItsDerivative(Entity numerator, Entity denominator, Entity.Variable x, bool integrateByParts) + { + Entity? linear = null; + var m = 0; + Entity constant = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(numerator)) + { + if (!factor.ContainsNode(x)) + { + constant *= factor; + continue; + } + if (linear is not null || factor is not Powf(var b, Number.Integer { EInteger: var power }) || !power.CanFitInInt32()) + return null; + (linear, m) = (b, power.ToInt32Unchecked()); + } + Entity? quadratic = null; + var n = 0; + foreach (var factor in Mulf.LinearChildren(denominator)) + { + if (!factor.ContainsNode(x)) + { + constant /= factor; + continue; + } + var (@base, power) = factor is Powf(var b, Number.Integer { EInteger: var p }) && p.CanFitInInt32() ? (b, p.ToInt32Unchecked()) : (factor, 1); + if (quadratic is not null) + return null; + (quadratic, n) = (@base, power); + } + if (linear is null || quadratic is null || m < 3 || m % 2 == 0 || n < 1 + || !TreeAnalyzer.TryGetPolyLinear(linear, x, out var p1, out var q1) || p1.ContainsNode(x) || q1.ContainsNode(x) + || !TreeAnalyzer.TryGetPolynomial(quadratic, x, out var read) || read.Count == 0 + || !read.Keys.All(k => k.Sign >= 0 && k.CompareTo(EInteger.FromInt32(2)) <= 0) || !read.ContainsKey(EInteger.FromInt32(2))) + return null; + Entity Coefficient(int k) => read.TryGetValue(EInteger.FromInt32(k), out var c) ? c : Number.Integer.Zero; + var (a2, a1, a0) = (Coefficient(2), Coefficient(1), Coefficient(0)); + if (a2.ContainsNode(x) || a1.ContainsNode(x) || a0.ContainsNode(x) + || !Functions.PartialFractions.IsZeroAsAValue(p1 * a1 - 2 * a2 * q1)) + return null; + var lambda = p1 / (2 * a2); + var u = Variable.CreateUnique(numerator / denominator, "u_quadratic"); + var inU = (constant * MathS.Pow(lambda, Number.Integer.Create(m)) + * MathS.Pow(4 * a2 * u + a1 * a1 - 4 * a2 * a0, Number.Integer.Create((m - 1) / 2)) + / MathS.Pow(u, Number.Integer.Create(n))).InnerSimplified; + if (Integration.ComputeAsAQuestionOfItsOwn(inU, u, integrateByParts) is not { } inTermsOfU + || inTermsOfU.Nodes.Any(node => node == MathS.NaN)) + return null; + return inTermsOfU.Substitute(u, quadratic); + } + /// /// A quotient of polynomials, split into two smaller quotients and integrated in two /// parts — at a rational root of the denominator where it has one, and otherwise at a @@ -701,6 +765,12 @@ Entity Back(Entity mapped) if (!TryReadAsQuotient(expr, out var numerator, out var denominator)) return null; + // An odd power of a multiple of the denominator's derivative over a power of a quadratic, + // in the quadratic: a polynomial over a power of one variable, where the partial + // fractions wrote seventy thousand characters for `(b + 2c x)^9/(a + b x + c x^2)^3`. + if (InTheQuadraticAnOddPowerOfItsDerivative(numerator, denominator, x, integrateByParts) is { } inTheQuadratic) + return inTheQuadratic; + // A quotient with x below a bar inside it, `1/(a + b/x)`, written over one bar: every // rule below reads the numerator and the denominator as polynomials, and `a + b/x` is // not one, where `x/(a x + b)` is read at once. Once: what one bar gives has none. diff --git a/Sources/Tests/UnitTests/Calculus/AnOddPowerOfAQuadraticsDerivativeIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/AnOddPowerOfAQuadraticsDerivativeIntegralTest.cs new file mode 100644 index 000000000..adba66500 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AnOddPowerOfAQuadraticsDerivativeIntegralTest.cs @@ -0,0 +1,50 @@ +// +// 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 +{ + /// + /// An odd power of a multiple of a quadratic's derivative over a power of the quadratic, in the + /// quadratic: a few powers, where the partial fractions wrote tens of thousands of characters. + /// Rubi's 1.2.1.2. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class AnOddPowerOfAQuadraticsDerivativeIntegralTest + { + [Theory] + [InlineData("(b + 2*c*x)^9/(a + b*x + c*x^2)^3")] + [InlineData("(b*d + 2*c*d*x)^9/(a + b*x + c*x^2)^3")] + public void InTheQuadratic(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Assert.True(text.Length < 2000, $"{text.Length} characters of answer for {integrand}"); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.4).Substitute("c", 1.9).Substitute("d", 1.6).Substitute("f", 0.7).Substitute("g", 0.45); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -2.1, -0.4, 0.3, 0.6, 1.6, 2.5 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart) + < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)), + $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + } +}