diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 539126d84..a6f0a85da 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -235,6 +235,20 @@ after it
|---|---|---|
| `"(a*c + b*c*x)^(-3-2*p)*(f + g*x)*(a^2 + 2*a*b*x + b^2*x^2)^p".ToEntity().Integrate("x")` | `integral(...)`; an answer with no value on the unreleased master | the antiderivative |
+### 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 |
+
### Half-odd powers of `a + a sec` and `c + d sec` together are integrated in the half angle's sine
**Answers where there were none.** `sec(e + f x) sqrt(a + a sec(e + f x))/sqrt(c + d sec(e + f x))` was declined,
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 610b093d5..a825dce58 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 power of a sum in the numerator whose coefficients share a symbol, `(b d + 2c d x)^9`,
// is that symbol's power times one of a sum without it: written so, it is a power of the
// denominator's derivative, and the division of the improper fraction as written ran past
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}");
+ }
+ }
+}