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}"); + } + } + } +}