diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index cc36da245..22579126a 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -396,6 +396,23 @@ in Rubi's 1.1.3.4 come out shorter as well, and those that took two seconds take | `"1/(x^8*(a + b*x^3)*(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | 1,217 characters | | `"1/(x^6*(a + b*x^4)*(c + d*x^4))".ToEntity().Integrate("x")` | `integral(...)` | 795 characters | +### A linear below the cube root of a quadratic is integrated in closed form + +**Answers where there were none.** `1/((d + e x) (d^2 + 3 e^2 x^2)^(1/3))` was declined, and so were six more of Rubi's +1.2.1.2. With `y = x + b/(2c)`, `1/((d + e x) (a + b x + c x^2)^(1/3))` is a linear in `y` below the cube root of +`a' + c y^2`, and where `c d'^2 = 3 a' e^2` or `c d'^2 = -9 a' e^2` it has a closed form of two or three logarithms and +an arctangent ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"1/((d+k*x)*(d^2+3*k^2*x^2)^(1/3))".ToEntity().Integrate("x")` | `integral(...)` | 773 characters | +| `"1/((2+3*i*x)*(4-27*x^2)^(1/3))".ToEntity().Integrate("x")` | `integral(...)` | 398 characters | +| `"1/((1+x^2)^(1/3)*(x+sqrt(3)))".ToEntity().Integrate("x")` | `integral(...)` | 290 characters | +| `"1/((1+x^2)^(1/3)*(-x+sqrt(3)))".ToEntity().Integrate("x")` | `integral(...)` | 302 characters | +| `"1/((d+k*x)*(d^2-9*k^2*x^2)^(1/3))".ToEntity().Integrate("x")` | `integral(...)` | 397 characters | +| `"1/((d+k*x)*(c^2*d^2-b*c*d*k+b^2*k^2+3*b*c*k^2*x+3*c^2*k^2*x^2)^(1/3))".ToEntity().Integrate("x")` | `integral(...)` | 1,934 characters | +| `"1/((d+k*x)*(-c^2*d^2+b*c*d*k+2*b^2*k^2+9*b*c*k^2*x+9*c^2*k^2*x^2)^(1/3))".ToEntity().Integrate("x")` | `integral(...)` | 1,274 characters | + ### A linear and a quadratic to one power are integrated as one binomial **Answers where there were none.** `1/((1 + x)^(1/3) (1 - x + x^2)^(1/3))` was declined. A linear and a quadratic to diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 5a0a8ccab..d7dab662e 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -19988,6 +19988,83 @@ Piecewise piecewise when piecewise.Cases.Any() => piecewise.Cases.First().Expres return null; } + /// + /// A linear below the cube root of a quadratic, 1/((d + e x) (a + b x + c x^2)^(1/3)), + /// in closed form where the shifted quadratic a' + c y^2, y = x + b/(2 c), and + /// the linear d' + e y satisfy c d'^2 = 3 a' e^2 or c d'^2 = -9 a' e^2. + /// + /// + /// With q^3 = 6 c^2 e^2/d'^2 and R = (a + b x + c x^2)^(1/3) the integral is + /// -sqrt(3) c e/(d'^2 q^2) arctan(1/sqrt(3) + 2 c (d' - e y)/(sqrt(3) d' q R)) + /// - 3 c e/(2 d'^2 q^2) ln(d + e x) + 3 c e/(2 d'^2 q^2) ln(c d' - c e y - d' q R), Rubi's + /// 1.2.1.2 rule. Where c d'^2 = -9 a' e^2, with t = e y/d', u = 1 - 3 t + /// and k^3 = a', it is (-3/8 ln(2 R + k u) + 1/4 ln(d + e x) + 1/8 ln(u) + /// - sqrt(3)/4 arctan(sqrt(3) k u/(4 R - k u)))/(e k): on the curve + /// w^3 = 1 - 9 t^2 the tangent at each point over t = -1 meets it again at + /// (1/3, 0), so w + r (1 - 3 t)/2 for the cube roots of unity r are the + /// functions whose logarithms carry the residues. Otherwise the integral is elliptic. The + /// answer is differentiated back. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + internal static Entity? SolveALinearBelowACubeRootOfAQuadraticInClosedForm(Entity expr, Entity.Variable x) + { + var (numerator, denominator) = Functions.SingleQuotient.Of(expr); + if (numerator.ContainsNode(x)) + return null; + Entity? linear = null, quadratic = null; + foreach (var factor in Mulf.LinearChildren(denominator)) + { + if (!factor.ContainsNode(x)) + numerator /= factor; + else if (quadratic is null && factor is Powf(var @base, Number.Rational power) && power == Number.Rational.Create(1, 3)) + quadratic = @base; + else if (linear is null && TreeAnalyzer.TryGetPolyLinear(factor, x, out _, out _)) + linear = factor; + else + return null; + } + if (linear is null || quadratic is null + || !TreeAnalyzer.TryGetPolyLinear(linear, x, out var e, out var d) + || !TreeAnalyzer.TryGetPolynomial(quadratic, x, out var read) + || read.Keys.Any(key => key.Sign < 0 || key.CompareTo(EInteger.FromInt32(2)) > 0) + || !read.TryGetValue(EInteger.FromInt32(2), out var c) || VanishesIdentically(c.Expand())) + return null; + var a = read.TryGetValue(EInteger.Zero, out var a0) ? a0 : Number.Integer.Zero; + var b = read.TryGetValue(EInteger.One, out var b0) ? b0 : Number.Integer.Zero; + var shift = (b / (2 * c)).InnerSimplified; + var dShifted = (d - e * shift).InnerSimplified; + var aShifted = (a - b * b / (4 * c)).InnerSimplified; + if (VanishesIdentically(dShifted.Expand()) || VanishesIdentically(e.Expand())) + return null; + var three = Number.Integer.Create(3); + var root3 = MathS.Sqrt(three); + if (VanishesIdentically((c * dShifted * dShifted + 9 * aShifted * e * e).Expand().InnerSimplified.Expand())) + { + var k = MathS.Pow(aShifted, Number.Rational.Create(1, 3)); + var cubeRoot = MathS.Pow(quadratic, Number.Rational.Create(1, 3)); + var u = 1 - 3 * e * (x + shift) / dShifted; + var closed = numerator / (e * k) * ( + -Number.Rational.Create(3, 8) * MathS.Ln(2 * cubeRoot + k * u) + + Number.Rational.Create(1, 4) * MathS.Ln(linear) + + Number.Rational.Create(1, 8) * MathS.Ln(u) + - root3 / 4 * MathS.Arctan(root3 * k * u / (4 * cubeRoot - k * u))); + closed = closed.InnerSimplified; + return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(closed, expr, x) ? closed : null; + } + if (!VanishesIdentically((c * dShifted * dShifted - 3 * aShifted * e * e).Expand().InnerSimplified.Expand())) + return null; + var q = MathS.Pow(6 * c * c * e * e / (dShifted * dShifted), Number.Rational.Create(1, 3)); + var r = MathS.Pow(quadratic, Number.Rational.Create(1, 3)); + var y = x + shift; + var scale = c * e / (dShifted * dShifted * q * q); + var answer = numerator * ( + -root3 * scale * MathS.Arctan(1 / root3 + 2 * c * (dShifted - e * y) / (root3 * dShifted * q * r)) + - three / 2 * scale * MathS.Ln(linear) + + three / 2 * scale * MathS.Ln(c * dShifted - c * e * y - dShifted * q * r)); + answer = answer.InnerSimplified; + return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null; + } + /// /// A power of a linear that is not whole, times a linear, over a whole power of a quadratic, /// with the power of the quadratic lowered one at a time: diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index 606c7cfd4..dc25a94de 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -668,6 +668,8 @@ private static Entity WithItsPiecewiseSumsCombined(Entity expr) // Two linears to one power that is not whole, as one quadratic to that power, before each // linear is written once: `(2 + 3x)^4/((1 - 2x)^(5/2) (3 + 5x)^(5/2))` took forty-five // seconds there, and over `(3 - x - 10x^2)^(5/2)` it takes half of one. + // A linear below the cube root of a quadratic, in closed form where Rubi has one. + if ((answer = IndefiniteIntegralSolver.SolveALinearBelowACubeRootOfAQuadraticInClosedForm(expr, x)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveTwoLinearsToOnePowerAsOneQuadratic(expr, x, integrateByParts)) is { }) return answer; // A linear and a quadratic to one power as one binomial, `(1 + x) (1 - x + x^2)` as // `1 + x^3`, which the rules for a power of a binomial answer. diff --git a/Sources/Tests/UnitTests/Calculus/ALinearBelowACubeRootOfAQuadraticIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ALinearBelowACubeRootOfAQuadraticIntegralTest.cs new file mode 100644 index 000000000..c9c808561 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ALinearBelowACubeRootOfAQuadraticIntegralTest.cs @@ -0,0 +1,75 @@ +// +// 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 linear below the cube root of a quadratic, 1/((d + e x) (a + b x + c x^2)^(1/3)), + /// where the shifted quadratic and the linear satisfy c d^2 = 3 a e^2 or + /// c d^2 = -9 a e^2: Rubi's 1.2.1.2 closed forms, an arctangent and logarithms. Sampled on both sides of the linear's root, + /// and where the quadratic is negative, so the cube root is complex. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class ALinearBelowACubeRootOfAQuadraticIntegralTest + { + 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}"); + } + } + + [Theory] + [InlineData("1/((1+x^2)^(1/3)*(x+sqrt(3)))")] + [InlineData("1/((1+x^2)^(1/3)*(-x+sqrt(3)))")] + [InlineData("1/((2+3*i*x)*(4-27*x^2)^(1/3))")] + [InlineData("1/((1+x)*(1-9*x^2)^(1/3))")] + public void WithNumbersIsAnsweredAndDifferentiatesBack(string integrand) + => DifferentiatesBack(integrand); + + [Theory] + [InlineData("1/((d+k*x)*(d^2+3*k^2*x^2)^(1/3))")] + [InlineData("1/((d+k*x)*(c^2*d^2-b*c*d*k+b^2*k^2+3*b*c*k^2*x+3*c^2*k^2*x^2)^(1/3))")] + [InlineData("1/((d+k*x)*(d^2-9*k^2*x^2)^(1/3))")] + [InlineData("1/((d+k*x)*(-c^2*d^2+b*c*d*k+2*b^2*k^2+9*b*c*k^2*x+9*c^2*k^2*x^2)^(1/3))")] + public void WithSymbolsIsAnsweredAndDifferentiatesBack(string integrand) + { + DifferentiatesBack(integrand, ("b", 2), ("c", 3), ("d", 0.7), ("k", 0.5)); + DifferentiatesBack(integrand, ("b", -2), ("c", 3), ("d", -0.7), ("k", 1.5)); + } + + [Fact] + public void IsNotAnsweredInClosedFormWithoutTheRelation() + { + // c d^2 = 2 a e^2: the integral is elliptic, and no closed form is claimed. + var integral = "1/((1+x)*(1+2*x^2)^(1/3))".ToEntity().Integrate("x"); + Assert.DoesNotContain("arctan", integral.Stringize()); + } + } +}