diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 34988dd16..3273ba5bf 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -429,6 +429,24 @@ root of a linear, the linear radical substitution gets there first | `"sin(a + b/sqrt(c + d*x))/(e + f*x)".ToEntity().Integrate("x")` | `integral(...)` | 1,114 characters | | `"sin(a + b/sqrt(c + d*x))/(e + f*x)^2".ToEntity().Integrate("x")` | `integral(...)` | 2,288 characters | +### A symbolic trinomial in x^2 beside a root of a quadratic is split over its factors + +**Answers where there were none.** `sqrt(d + k x^2)/(a + b x^2 + c x^4)` and its kin were declined, most of them +past any budget. The trinomial is written over its factors in x^2 now, with its roots named: `c (x^2 - r)(x^2 - s)` +where they are real, and `c (x^2 + p x + m)(x^2 - p x + m)` where they are a conjugate pair. Each factor is a quadratic +the existing rules close beside the root. The answer is a piecewise on the sign of `b^2 - 4 a c`, and the second +case is most of its length ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x^2*sqrt(d + k*x^2)/(a + b*x^2 + c*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 29,139 characters | +| `"sqrt(d + k*x^2)/(x^2*(a + b*x^2 + c*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 10,736 characters | +| `"(d + k*x^2)^(3/2)/(a + b*x^2 + c*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 31,242 characters | +| `"x^4*(d + k*x^2)^(3/2)/(a + b*x^2 + c*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 41,265 characters | +| `"x^2*sqrt(1 - x^2)/(a + b*x^2 + c*x^4)".ToEntity().Integrate("x")` | `integral(...)` | 8,502 characters | +| `"x^4/((a + b*x^2 + c*x^4)*sqrt(d + k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 28,551 characters | +| `"1/(x^2*(a + b*x^2 + c*x^4)*sqrt(d + k*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 10,194 characters | + ### A power of the cosine beside a secant binomial is integrated in the cosine **Answers where there were none.** `cos(c + d x)^2/(a + b sec(c + d x))` was declined, and so were the cosine's powers diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 11ef427c4..f8bbfe1c3 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -18722,6 +18722,104 @@ static Entity WithoutConditions(Entity coefficient) return answer.Nodes.Any(node => node is Number.Complex { IsNaN: true }) ? null : answer; } + /// + /// 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. + /// + /// + /// 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 + /// (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 + /// + internal static Entity? SolveATrinomialInTheSquareBesideTheRootOfAQuadratic(Entity expr, Entity.Variable x) + { + // A function of x^2: every x in an even power of it, the root's quadratic among them. + // `sqrt(x)/(a + b x^2 + c x^4)^2` is not one, and a power of x to an odd degree below + // the bar is a function of x^2 after u = x^2, which reads it more shortly. + if (!expr.Nodes.Any(node => node is Powf(var radicand, Number.Rational { ERational.Denominator: var half }) + && half.Equals(EInteger.FromInt32(2)) && radicand.ContainsNode(x) + && TreeAnalyzer.TryGetPolyQuadratic(radicand, x, out var squared, out _, out _) && !VanishesIdentically(squared)) + || expr.Nodes.Count(node => node == x) != expr.Nodes.Count(node => node is Powf(var @base, Number.Integer { EInteger.IsEven: true }) && @base == x)) + return null; + var (_, denominator) = Functions.SingleQuotient.Of(expr); + Entity? trinomial = null; + (Entity A, Entity B, Entity C)? read = null; + foreach (var factor in Mulf.LinearChildren(denominator)) + { + var @base = factor is Powf(var repeated, Number.Integer { EInteger.Sign: > 0 }) ? repeated : factor; + if (@base is not (Sumf or Minusf) || !@base.ContainsNode(x) || !@base.Vars.Any(symbol => symbol != x) + || !TreeAnalyzer.TryGetPolynomial(@base, x, out var monomials) + || monomials.Count != 3 + || !monomials.TryGetValue(EInteger.Zero, out var a) + || !monomials.TryGetValue(EInteger.FromInt32(2), out var b) + || !monomials.TryGetValue(EInteger.FromInt32(4), out var c)) + continue; + if (trinomial is not null && trinomial != @base) + return null; + trinomial = @base; + read = (a, b, c); + } + if (trinomial is null || read is not var (constant, middle, leading) + || VanishesIdentically(leading) || VanishesIdentically(middle)) + return null; + 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. + 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); + return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; + } + /// /// A piece over a whole power of , beside its root, as the one /// half-odd power the two are: (g + h x)/(Q^j sqrt(Q)) is diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index bea48da67..3ccccf2e2 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -666,6 +666,10 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // the question asked only, and the polynomial term below writes `1/u` as `u^(-1)` and // asks that a level down: `1/(e^atanh(a x) (c - c/(a x))^(3/2))` reached it nowhere. if ((answer = IndefiniteIntegralSolver.SolveByWritingEachLinearOnce(expr, x, integrateByParts)) is { }) return answer; + // A symbolic trinomial in x^2 below the bar beside a root of a quadratic, over its roots + // in x^2, before anything reads the trinomial whole: asked of it, the routes below ran + // past a minute on Rubi's 1.2.2.4 `sqrt(d + e x^2)/(x^2 (a + b x^2 + c x^4))`. + if ((answer = IndefiniteIntegralSolver.SolveATrinomialInTheSquareBesideTheRootOfAQuadratic(expr, x)) is { }) return answer; // A linear under a power inside a sum, written as the variable: `x^3/(a + b (c + d x)^3)`. // Before the polynomial term, which takes the leading coefficient out of a power of // the sum and writes what is left out in powers of x, where the linear is gone; and diff --git a/Sources/Tests/UnitTests/Calculus/ATrinomialInTheSquareBesideARootIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ATrinomialInTheSquareBesideARootIntegralTest.cs new file mode 100644 index 000000000..e526c1012 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ATrinomialInTheSquareBesideARootIntegralTest.cs @@ -0,0 +1,61 @@ +// +// 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 with a symbolic trinomial in x^2 below the bar, beside a root of a + /// quadratic: taken apart over the trinomial's roots in x^2 where they are real, and over two + /// real quadratics where they are a conjugate pair. Rubi's 1.2.2.4, written here with + /// k for e, which is Euler's number. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ATrinomialInTheSquareBesideARootIntegralTest + { + private static void DifferentiatesBack(string integrand, params (string, double)[] pins) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + + // Pinned before it is differentiated: the answer is a piecewise on the sign of the + // discriminant, read where it is a number. + 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[] { -0.7, -0.3, 0.4, 0.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}"); + } + } + + [Theory] + [InlineData("x^2*sqrt(d+k*x^2)/(a+b*x^2+c*x^4)")] + [InlineData("sqrt(d+k*x^2)/(x^2*(a+b*x^2+c*x^4))")] + [InlineData("sqrt(1-x^2)/(x^2*(a+b*x^2+c*x^4))")] + public void IsAnsweredAndDifferentiatesBack(string integrand) + { + // Two real roots in x^2, then a conjugate pair. + DifferentiatesBack(integrand, ("a", 1), ("b", 5), ("c", 2), ("d", 1.5), ("k", 2)); + DifferentiatesBack(integrand, ("a", 2), ("b", 1), ("c", 1.5), ("d", 1.25), ("k", 3)); + } + } +}