diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
index 4f92adc78..03524e502 100644
--- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs
@@ -19153,27 +19153,28 @@ static Entity WithoutConditions(Entity coefficient)
///
/// A rational function with a trinomial in the square below the bar, beside a root of a
- /// quadratic, taken apart over the trinomial's factors in the square: where its roots in
- /// x^2 are real, a + b x^2 + c x^4 is c (x^2 - r) (x^2 - s) with
- /// r, s = (-b -+ sqrt(b^2 - 4 a c))/(2c), and where they are a conjugate pair it is
- /// c (x^2 + p x + m) (x^2 - p x + m) with m = sqrt(a/c) and
- /// p = sqrt(2 m - b/c). Each factor is a quadratic the rule before this one splits
- /// the integrand over and closes beside the root, and the answer is the piecewise of the
- /// two on the sign of the discriminant. Rubi's 1.2.2.4
- /// sqrt(d + e x^2)/(a + b x^2 + c x^4) was declined.
+ /// quadratic, taken apart over the trinomial's roots in the square: a + b x^2 + c x^4
+ /// is c (x^2 - r) (x^2 - s) with r, s = (-b -+ sqrt(b^2 - 4 a c))/(2c), and each
+ /// factor is a quadratic the rule before this one splits the integrand over and closes
+ /// beside the root. Rubi's 1.2.2.4 sqrt(d + e x^2)/(a + b x^2 + c x^4) was declined.
///
///
- /// Asked with r, s, m and p symbols of their own and written
- /// back after: the integrand so written is an integrand for every value of them, and the
- /// one asked for at theirs. Named, each stays one atom through the split: with
+ /// Asked with r and s symbols of their own and written back after: the
+ /// integrand so written is an integrand for every value of them, and the one asked for at
+ /// theirs. Named, each stays one atom through the split: with
/// (sqrt(b^2 - 4 a c) - b)/(2c) in place of r the split declined
- /// x^2 sqrt(d + e x^2)/(a + b x^2 + c x^4), and over x^2 - r it is 1,458
- /// characters (#1877
- /// was the same with a quadratic). The real roots' writing alone is not enough: with
- /// r complex the pieces' cases read the sign of a number off the real line, and the
- /// answer was NaN wherever b^2 < 4 a c. Not where the discriminant vanishes,
- /// the generic case's. With a symbol in the trinomial only, and in a function of x^2 only.
- /// https://github.com/asc-community/AngouriMath/issues/718
+ /// x^2 sqrt(d + e x^2)/(a + b x^2 + c x^4)
+ /// (#1877 was the
+ /// same with a quadratic). The same writing where the roots are a conjugate pair: each
+ /// piece over x^2 - r is an arctangent of w x/sqrt(d + e x^2) with
+ /// w^2 = (d + e r)/(-r), which holds on either branch of w, and for r
+ /// off the real line w x/sqrt(d + e x^2) stays off the arctangent's cuts, so the
+ /// two conjugate pieces add to a real, continuous antiderivative. Written instead over
+ /// the two real quadratics x^2 -+ p x + m, the answer to
+ /// x^2 sqrt(d + e x^2)/(a + b x^2 + c x^4) was 29,139 characters where this one is
+ /// under 2,000. Not where the discriminant vanishes, the generic case's. With a symbol in
+ /// the trinomial only, and in a function of x^2 only.
+ /// https://github.com/asc-community/AngouriMath/issues/1885
///
internal static Entity? SolveATrinomialInTheSquareBesideTheRootOfAQuadratic(Entity expr, Entity.Variable x)
{
@@ -19209,43 +19210,20 @@ static Entity WithoutConditions(Entity coefficient)
var discriminant = (middle * middle - 4 * constant * leading).InnerSimplified;
if (VanishesIdentically(discriminant))
return null;
- // Two writings of the trinomial, each real where it is taken, its roots and
- // coefficients named so that each stays one atom through the split. Two real roots
- // in x^2: c (x^2 - r) (x^2 - s). None: the roots are a conjugate pair and the pieces' cases
- // read the sign of an imaginary q, so it is the product of two real quadratics
- // c (x^2 + p x + m) (x^2 - p x + m) with m = sqrt(a/c) and p = sqrt(2 m - b/c), both
- // real there since b^2 < 4 a c makes a/c positive and |b/c| < 2 m. Each is an
- // identity in its symbols, so each answer is an antiderivative wherever its symbols
- // are what they stand for. A root twice, where the discriminant vanishes, is the
- // case the generic answer leaves, as `ln(a x + b)/a` leaves a = 0.
+ // c (x^2 - r) (x^2 - s), its roots named so that each stays one atom through the split.
+ // An identity in r and s, so the answer is an antiderivative wherever they are the
+ // roots, a conjugate pair included. A root twice, where the discriminant vanishes, is
+ // the case the generic answer leaves, as `ln(a x + b)/a` leaves a = 0.
var square = MathS.Pow(x, Number.Integer.Create(2));
var r = Variable.CreateUnique(expr, "r");
var s = Variable.CreateUnique(expr + r, "s");
- var m = Variable.CreateUnique(expr + r + s, "m");
- var p = Variable.CreateUnique(expr + r + s + m, "p");
var q = MathS.Sqrt(discriminant);
- var writings = new (Entity Written, Entity[] Named, Entity[] Values, Entity Condition)[]
- {
- (leading * (square - r) * (square - s),
- new Entity[] { r, s }, new[] { (q - middle) / (2 * leading), (-q - middle) / (2 * leading) }, discriminant > 0),
- (leading * (square + p * x + m) * (square - p * x + m),
- new Entity[] { m, p }, new[] { MathS.Sqrt(constant / leading), MathS.Sqrt(2 * MathS.Sqrt(constant / leading) - middle / leading) }, discriminant < 0),
- };
- var cases = new List();
- foreach (var (overTheRoots, named, values, condition) in writings)
- {
- var written = expr.Replace(node => node == trinomial ? overTheRoots : node);
- if (SolveARationalFunctionBesideTheRootOfAQuadratic(written, x) is not { } inNamed)
- return null;
- var answered = inNamed;
- for (var i = named.Length - 1; i >= 0; i--)
- answered = answered.Substitute(named[i], values[i]);
- answered = answered.InnerSimplified;
- if (answered.Nodes.Any(node => node is Number.Complex { IsNaN: true }))
- return null;
- cases.Add(new Providedf(answered, condition));
- }
- var answer = MathS.Piecewise(cases);
+ var written = expr.Replace(node => node == trinomial ? leading * (square - r) * (square - s) : node);
+ if (SolveARationalFunctionBesideTheRootOfAQuadratic(written, x) is not { } inNamed)
+ return null;
+ var answer = inNamed.Substitute(s, (-q - middle) / (2 * leading)).Substitute(r, (q - middle) / (2 * leading)).InnerSimplified;
+ if (answer.Nodes.Any(node => node is Number.Complex { IsNaN: true }))
+ return null;
return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null;
}
diff --git a/Sources/Tests/UnitTests/Calculus/ATrinomialWithConjugateRootsInTheSquareIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ATrinomialWithConjugateRootsInTheSquareIntegralTest.cs
new file mode 100644
index 000000000..4470e12de
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ATrinomialWithConjugateRootsInTheSquareIntegralTest.cs
@@ -0,0 +1,56 @@
+//
+// 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 rational function over a + b x^2 + c x^4 beside a root of d + k x^2, taken
+ /// apart over the trinomial's roots in x^2 whether they are real or a conjugate pair. Over the
+ /// two real quadratics a conjugate pair also factors into, the answer to
+ /// x^2 sqrt(d + k x^2)/(a + b x^2 + c x^4) was 29,139 characters. Rubi's 1.2.2.4.
+ /// #1885
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ATrinomialWithConjugateRootsInTheSquareIntegralTest
+ {
+ [Theory]
+ // b^2 < 4 a c: the roots in x^2 are a conjugate pair.
+ [InlineData("x^2*sqrt(d + k*x^2)/(a + b*x^2 + c*x^4)", 2, 1, 3)]
+ [InlineData("sqrt(d + k*x^2)/(a + b*x^2 + c*x^4)", 2, 1, 3)]
+ [InlineData("x^4/((a + b*x^2 + c*x^4)*sqrt(d + k*x^2))", 2, -1, 3)]
+ [InlineData("(d + k*x^2)^(3/2)/(a + b*x^2 + c*x^4)", 2, 1, 3)]
+ // b^2 > 4 a c: two real roots, the same writing.
+ [InlineData("x^2*sqrt(d + k*x^2)/(a + b*x^2 + c*x^4)", 1, 5, 2)]
+ public void OverItsRootsInTheSquare(string integrand, double a, double b, double c)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ var text = integral.Stringize();
+ Assert.DoesNotContain("integral(", text);
+ Assert.True(text.Length < 5000, $"{text.Length} characters of answer for {integrand}");
+ Entity Pinned(Entity e) => e.Substitute("a", a).Substitute("b", b).Substitute("c", c).Substitute("d", 1.9).Substitute("k", 0.7);
+ var antiderivative = Pinned(integral.Substitute("C", 0));
+ var derivative = antiderivative.Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ foreach (var at in new[] { -1.5, -0.7, 0.1, 0.57, 1.4, 2.9 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
+ < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)),
+ $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
+ // And the antiderivative itself is real there: the two conjugate pieces add to one.
+ var value = antiderivative.Substitute("x", at).EvalNumerical();
+ Assert.True(Math.Abs((double)value.ImaginaryPart) < 1e-9 * Math.Max(1, Math.Abs((double)value.RealPart)),
+ $"the antiderivative of {integrand} is {value} at x = {at}");
+ }
+ }
+ }
+}