diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index f0c95a6fc..69f8bbf7d 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -55,6 +55,22 @@ numbers for every coefficient the solve is now done in exact rationals, to degre
| `"1/((3-2*x+x^2)^(11/2)*(1+x+2*x^2)^5)".ToEntity().Integrate("x")` | `integral(...)` | 1,853 characters |
| `"1/((3-2*x+x^2)^(21/2)*(1+x+2*x^2)^10)".ToEntity().Integrate("x")` | `integral(...)` | 3,475 characters |
+### A polynomial over a symbolic quartic that is two quadratics in a hidden variable is split over them
+
+**Answers where there were none.** `1/(a + b x + c x^2 + b x^3 + a x^4)` was declined, and so were Rubi's 1.3.1
+`(A + B x + C x^2 + D x^3)/(a + b x + c x^2 + b x^3 + a x^4)` and `1/(4 a c + 4 c^2 x^2 + 4 c d x^3 + d^2 x^4)`. A
+palindromic quartic is `x^2` times a quadratic in `x + 1/x`, and a quartic whose odd terms vanish under
+`y = x + c/d` is a quadratic in `y^2`; either is the product of two quadratics in x whose
+coefficients are the roots of that quadratic, and the polynomial over it is split over the two
+([#718](https://github.com/asc-community/AngouriMath/issues/718)).
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"1/(a+b*x+c*x^2+b*x^3+a*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 1,620 characters |
+| `"(A+B*x+C*x^2+D*x^3)/(a+b*x+c*x^2+b*x^3+a*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 3,416 characters |
+| `"1/(4*a*c+4*c^2*x^2+4*c*d*x^3+d^2*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 3,920 characters |
+| `"x/(4*a*c+4*c^2*x^2+4*c*d*x^3+d^2*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 2,315 characters |
+
### `|` is divisibility, and was disjunction
**Read this one first.** It is the only entry here that changes what existing input *means* rather
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index ded68afa2..34f2655f3 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -29302,6 +29302,93 @@ bool HoldsItsValue(Entity factor)
=> Functions.PartialFractions.HoldsAtSampledPoints(factor, factor.Substitute(x, Number.Rational.Create(3, 10)), x);
}
+ ///
+ /// A polynomial over a quartic with symbols in it that splits into two quadratics in a hidden
+ /// variable: a palindromic one, c x^4 + b x^3 + e x^2 + b x + c, is
+ /// c (x^2 - p x + 1)(x^2 - q x + 1) with p and q the roots of
+ /// c w^2 + b w + e - 2 c, since it is x^2 times a quadratic in w = x + 1/x;
+ /// and one whose odd terms vanish under y = x + c3/(4 c4) is c4 (y^2 - p)(y^2 - q).
+ /// The integral over the factored quartic is asked with p and q named, and they are
+ /// written back.
+ ///
+ ///
+ /// The integral over two real quadratics splits on the signs of their discriminants, which mean
+ /// nothing for roots that may be complex; the first arm differentiates back for any of them, and
+ /// the answer is checked. A quartic already even is the biquadratic rule's. Rubi's 1.3.1
+ /// (A + B x + C x^2 + D x^3)/(a + b x + c x^2 + b x^3 + a x^4) and
+ /// 1/(4 a c + 4 c^2 x^2 + 4 c d x^3 + d^2 x^4) were declined.
+ /// https://github.com/asc-community/AngouriMath/issues/718
+ ///
+ internal static Entity? SolveAPolynomialOverASymbolicQuarticThatSplitsIntoTwoQuadratics(Entity expr, Entity.Variable x, bool integrateByParts)
+ {
+ if (!Integration.AnsweringTheQuestionAsked || !TryReadAsQuotient(expr, out var numerator, out var denominator)
+ || !denominator.Vars.Any(v => v != x))
+ return null;
+ // A quartic written as one sum only: the factored one asked below is palindromic again once
+ // multiplied out, and read as one the rule split it again, four levels deep.
+ if (Mulf.LinearChildren(denominator).Count(factor => factor.ContainsNode(x)) != 1
+ || Mulf.LinearChildren(denominator).First(factor => factor.ContainsNode(x)) is not (Sumf or Minusf))
+ return null;
+ if (!TreeAnalyzer.TryGetPolynomial(denominator, x, out var read) || read.Keys.Any(key => key.Sign < 0 || key.CompareTo(EInteger.FromInt32(4)) > 0)
+ || !read.ContainsKey(EInteger.FromInt32(4)) || read.Values.Any(value => value.ContainsNode(x))
+ || !TreeAnalyzer.TryGetPolynomial(numerator, x, out var readAbove) || readAbove.Values.Any(value => value.ContainsNode(x)))
+ return null;
+ Entity At(int k) => read.TryGetValue(EInteger.FromInt32(k), out var value) ? value : Number.Integer.Zero;
+ var (c0, c1, c2, c3, c4) = (At(0), At(1), At(2), At(3), At(4));
+ if (VanishesIdentically(c1.Expand()) && VanishesIdentically(c3.Expand()))
+ return null;
+ var p = Variable.CreateUnique(expr, "p_quart");
+ var q = Variable.CreateUnique(expr + p, "q_quart");
+ Entity first, second, pValue, qValue;
+ if (VanishesIdentically((c0 - c4).Expand()) && VanishesIdentically((c1 - c3).Expand()))
+ {
+ var discriminant = (c3 * c3 - 4 * c4 * (c2 - 2 * c4)).Expand().InnerSimplified;
+ if (Functions.PartialFractions.IsZeroAsAValue(discriminant))
+ return null;
+ pValue = (-c3 + MathS.Sqrt(discriminant)) / (2 * c4);
+ qValue = (-c3 - MathS.Sqrt(discriminant)) / (2 * c4);
+ (first, second) = (MathS.Sqr(x) - p * x + 1, MathS.Sqr(x) - q * x + 1);
+ }
+ else
+ {
+ var shift = Functions.PartialFractions.InLowestTermsOverTheSymbols(c3 / (4 * c4));
+ // The coefficients in y = x + s by the binomial theorem, d_k = sum_j c_j C(j, k) (-s)^(j - k),
+ // each in lowest terms over the symbols: the shift puts `c/d` into them, and `d^2 (c/d)^3`
+ // beside `c^3/d` is not seen to cancel as written.
+ var coefficients = new[] { c0, c1, c2, c3, c4 };
+ int[,] binomial = { { 1, 0, 0, 0, 0 }, { 1, 1, 0, 0, 0 }, { 1, 2, 1, 0, 0 }, { 1, 3, 3, 1, 0 }, { 1, 4, 6, 4, 1 } };
+ Entity Shifted(int k)
+ {
+ Entity sum = Number.Integer.Zero;
+ for (var j = k; j <= 4; j++)
+ sum += coefficients[j] * binomial[j, k] * MathS.Pow(-shift, Number.Integer.Create(j - k));
+ return Functions.PartialFractions.InLowestTermsOverTheSymbols(sum);
+ }
+ if (!Functions.PartialFractions.IsZeroAsAValue(Shifted(1)) || !Functions.PartialFractions.IsZeroAsAValue(Shifted(3)))
+ return null;
+ var e2 = Functions.PartialFractions.InLowestTermsOverTheSymbols(Shifted(2) / c4);
+ var e0 = Functions.PartialFractions.InLowestTermsOverTheSymbols(Shifted(0) / c4);
+ var discriminant = (e2 * e2 - 4 * e0).Expand().InnerSimplified;
+ if (Functions.PartialFractions.IsZeroAsAValue(discriminant))
+ return null;
+ pValue = (-e2 + MathS.Sqrt(discriminant)) / 2;
+ qValue = (-e2 - MathS.Sqrt(discriminant)) / 2;
+ (first, second) = (MathS.Sqr(x + shift) - p, MathS.Sqr(x + shift) - q);
+ }
+ if (Integration.ComputeAsAQuestionOfItsOwn(numerator / (c4 * first * second), x, integrateByParts) is not { } inPQ)
+ return null;
+ inPQ = inPQ.Replace(node => node switch
+ {
+ Piecewise piecewise when piecewise.Cases.Any() => piecewise.Cases.First().Expression,
+ Providedf provided => provided.Expression,
+ _ => node,
+ });
+ // The roots written in without simplifying after: simplified, the answer to
+ // `1/(a + b x + c x^2 + b x^3 + a x^4)` multiplied out to 370,884 characters from about one thousand.
+ var answer = inPQ.InnerSimplified.Substitute(p, pValue).Substitute(q, qValue);
+ return Functions.PartialFractions.DerivativeDiffersWhereReal(answer, expr, x) ? null : answer;
+ }
+
///
/// A rational function R times the logarithm of G, a product of powers of linears
/// nested in powers and constants, by parts: integral R ln(G) = S ln(G) - integral S G'/G
diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
index 39247aeed..81af62a98 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs
@@ -664,6 +664,9 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr)
// A sum free of the variable that comes to a number multiplied out, as that number,
// before anything reads a coefficient or an exponent in it: `2 + 2 a - 2 (1 + a)`.
if ((answer = IndefiniteIntegralSolver.SolveByFoldingConstantSumsToTheirNumbers(expr, x, integrateByParts)) is { }) return answer;
+ // A polynomial over a symbolic quartic that splits into two quadratics in a hidden variable,
+ // palindromic or even after a shift: the rules below spent seconds declining both.
+ if ((answer = IndefiniteIntegralSolver.SolveAPolynomialOverASymbolicQuarticThatSplitsIntoTwoQuadratics(expr, x, integrateByParts)) is { }) return answer;
// A symbolic power of a sine, cosine, secant or cosecant times `alpha + beta f^2`, where it is
// the derivative of `cos sin^(m + 1)`, `sin cos^(m + 1)`, `tan sec^m` or `cot csc^m`.
if ((answer = IndefiniteIntegralSolver.SolveASymbolicPowerOfATrigonometricFunctionAsTheDerivativeOfAProduct(expr, x)) is { }) return answer;
diff --git a/Sources/Tests/UnitTests/Calculus/ASymbolicQuarticThatSplitsIntoTwoQuadraticsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ASymbolicQuarticThatSplitsIntoTwoQuadraticsIntegralTest.cs
new file mode 100644
index 000000000..395222263
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ASymbolicQuarticThatSplitsIntoTwoQuadraticsIntegralTest.cs
@@ -0,0 +1,65 @@
+//
+// 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
+{
+ ///
+ /// A polynomial over a symbolic quartic that splits into two quadratics in a hidden variable: a
+ /// palindromic one over x + 1/x, and one even after a shift. Rubi's 1.3.1
+ /// (A + B x + C x^2 + D x^3)/(a + b x + c x^2 + b x^3 + a x^4) and
+ /// 1/(4 a c + 4 c^2 x^2 + 4 c d x^3 + d^2 x^4).
+ /// #718
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ASymbolicQuarticThatSplitsIntoTwoQuadraticsIntegralTest
+ {
+ 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("(A+B*x+G*x^2+D*x^3)/(a+b*x+c*x^2+b*x^3+a*x^4)")]
+ [InlineData("1/(a+b*x+c*x^2+b*x^3+a*x^4)")]
+ [InlineData("1/(4*a*c+4*c^2*x^2+4*c*d*x^3+d^2*x^4)")]
+ [InlineData("x/(4*a*c+4*c^2*x^2+4*c*d*x^3+d^2*x^4)")]
+ public void IsAnsweredAndDifferentiatesBack(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x").Stringize();
+ // The factored quartic asked below is palindromic again; read as a fresh one, the answer
+ // ran to 803,488 characters.
+ Assert.True(integral.Length < 10_000, $"{integral.Length} characters");
+ DifferentiatesBack(integrand, ("A", 0.6), ("B", 1.25), ("G", 0.3), ("D", 0.75), ("a", 1.4), ("b", 0.5), ("c", 3), ("d", 2));
+ DifferentiatesBack(integrand, ("A", -1.5), ("B", 0.25), ("G", -0.4), ("D", -2), ("a", 0.5), ("b", -3), ("c", -1), ("d", -0.3));
+ }
+ }
+}