diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 80e69aa15..4cf77a562 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -1058,6 +1058,21 @@ secant too now, and the integrand is `sgn(cos(x)) (1 + u^2)^(3/2)/(a + b u)^2` i | `"sec(x)^7/(a + b*tan(x))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,253 characters | | `"sec(x)/(a + b*cot(x))".ToEntity().Integrate("x")` | `integral(...)` | 571 characters | +### A quadratic with rational roots takes Euler's third substitution at one of them + +**Answers where there were none.** `1/(x + sqrt(3 - 2x - x^2))` was declined. Its leading coefficient is negative and +its constant term not a square, so the second substitution wrote `sqrt(3)` into every coefficient and the rational +integrator could not finish. `3 - 2x - x^2` is `(1 - x)(3 + x)`: shifted to a root, the constant term is zero, and the +third substitution answers with rational coefficients throughout. A quadratic with a positive leading coefficient +keeps the first substitution, which was already answering it +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/(x + sqrt(3 - 2*x - x^2))".ToEntity().Integrate("x")` | `integral(...)` | 389 characters | +| `"1/(x + sqrt(3 - 2*x - x^2))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,160 characters | +| `"1/(2*x + sqrt(3 - 2*x - x^2))".ToEntity().Integrate("x")` | `integral(...)` | 253 characters | + ### A root of a multiple of the radicand beside Euler's sum is the same root **Answers where there were none.** `sqrt(g Q) (d + e x + f sqrt(Q))^n` was declined, with the root of `g Q` below the diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 557c5f1ba..12ba5197e 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -18870,6 +18870,21 @@ private static bool VanishesIdentically(Entity expr) return Functions.PartialFractions.Bare(inner.Simplify()).Evaled is Number.Complex { IsZero: true }; } + /// + /// A rational root of a x^2 + b x + c with rational coefficients, the larger, or + /// null where its roots are not rational. + /// + private static Entity? RationalRootOf(Entity a, Entity b, Entity c) + { + if (a.Evaled is not Number.Rational ra || b.Evaled is not Number.Rational rb || c.Evaled is not Number.Rational rc + || ra.Evaled is Number.Complex { IsZero: true }) + return null; + var discriminant = (rb * rb - 4 * ra * rc).InnerSimplified; + if (discriminant.Evaled is not Number.Rational { IsNegative: false } || MathS.Sqrt(discriminant).InnerSimplified.Evaled is not Number.Rational root) + return null; + return ((-rb + root) / (2 * ra)).InnerSimplified; + } + /// /// as a x^2 + b x + c, read through a constant below the /// bar as well: Rubi writes (a f^2 + e x (2 d + e x))/f^2 for @@ -19079,6 +19094,21 @@ node is Powf(var @base, Number.Rational r) && byRadicand.TryGetValue(@base, out else if (aRational) which = 1; else if (cRational) which = 2; else if (aPositive) which = 1; + else if (RationalRootOf(a, b, c) is { } rationalRoot) + { + // Neither root rational and a < 0, the quadratic's own roots are: `3 - 2x - x^2` + // is `(1 - x)(3 + x)`, and the second substitution wrote `sqrt(3)` into every + // coefficient, after which `1/(x + sqrt(3 - 2x - x^2))` was declined. Shifted to a + // root, x = y + r, the constant term is zero and the third substitution's, with + // rational coefficients throughout. Where a > 0 the first substitution answers + // first and faster: `sqrt(12x^2 + 17x + 6)/((2 + 3x)^2 (30 + 31x - 12x^2)^2)` + // took 1.4 s by it and timed out by the shift. + var y = Variable.CreateUnique(expr, "y_euler"); + if (SolveByEulerSubstitution(expr.Substitute(x, y + rationalRoot), y) is not { } inY) + return null; + var writtenBack = radicand.Substitute(x, y + rationalRoot).Substitute(y, x - rationalRoot); + return inY.Substitute(y, x - rationalRoot).Replace(node => node == writtenBack ? radicand : node).InnerSimplified; + } else if (cPositive) which = 2; else if (aValue is null) which = 1; else if (cValue is null) which = 2; diff --git a/Sources/Tests/UnitTests/Calculus/EulerSubstitutionTest.cs b/Sources/Tests/UnitTests/Calculus/EulerSubstitutionTest.cs index e281a6210..34efa8cc3 100644 --- a/Sources/Tests/UnitTests/Calculus/EulerSubstitutionTest.cs +++ b/Sources/Tests/UnitTests/Calculus/EulerSubstitutionTest.cs @@ -119,6 +119,18 @@ private static void DifferentiatesBack(string integrand, double[] points, params [InlineData("sqrt(x^2 + 2*x)/x", new[] { 0.3, 0.9, 1.7, 2.6 })] public void TheRootAtZero(string integrand, double[] points) => DifferentiatesBack(integrand, points, ("r", 1.7)); + /// + /// Neither sqrt(a) nor sqrt(c) rational, but the quadratic's roots: shifted + /// to one, the root is at zero and the third substitution has rational coefficients, + /// where the second wrote sqrt(3) into every one and was declined. Rubi's 1.3.2. + /// + [Theory] + [InlineData("1/(x + sqrt(3 - 2*x - x^2))", new[] { -2.5, -1.2, 0.3, 0.8 })] + [InlineData("1/(x + sqrt(3 - 2*x - x^2))^3", new[] { -2.5, -1.2, 0.3, 0.8 })] + [InlineData("x/(x + sqrt(3 - 2*x - x^2))", new[] { -2.5, -1.2, 0.3, 0.8 })] + [InlineData("1/(2*x + sqrt(3 - 2*x - x^2))", new[] { -2.5, -1.2, 0.3, 0.8 })] + public void ARationalRootOfTheQuadratic(string integrand, double[] points) => DifferentiatesBack(integrand, points); + /// /// Reached one level down, which is why the rule is not scoped: a nested radical under /// u = sqrt(1 + x), and the remainders of by parts against an inverse function.