diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 391832803..3d1e03d67 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -747,6 +747,21 @@ or of its square over the root. Rubi's 1.3.3 names those coefficients, and its r | `"(c - 2*d*x)/((c + d*x)*sqrt(c^3 + 4*d^3*x^3))".ToEntity().Integrate("x")` | `integral(...)` | `2 c/d` times an arctangent of `sqrt(3 c^3) (1 + 2 d x/c)/sqrt(c^3 + 4 d^3 x^3)` over `sqrt(3 c^3)`, or an inverse hyperbolic tangent, by the sign of `c^3` | | `"(1+x)/((x-2)*sqrt(1+x^3))".ToEntity().Integrate("x")` | `integral(...)` | `-(2/3) artanh((1 + x)^2/(3 sqrt(1 + x^3)))`, written as a logarithm | +### `x` over a cubic binomial beside the root of another is integrated where that is elementary + +**Answers where there were none.** `x/((a + b x^3) sqrt(c + d x^3))` is elementary at `4 b c = a d` +and at `8 b c + a d = 0`, Rubi's 1.1.3.4, as arctangents and inverse hyperbolic tangents of +`sqrt(c + d x^3)` and of `(1 + q x)/sqrt(c + d x^3)` for `q = (d/c)^(1/3)`, and Welz's +`x/((4 - x^3) sqrt(1 - x^3))` and the rest at those ratios were declined. They are answered in +closed form now, by the sign of `c` +([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x/((4 - x^3)*sqrt(1 - x^3))".ToEntity().Integrate("x")` | `integral(...)` | two arctangents and two inverse hyperbolic tangents, of `sqrt(1 - x^3)` and of `(1 - 2^(1/3) x)/sqrt(1 - x^3)` and its kind | +| `"x/((4*c + d*x^3)*sqrt(c + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | the same in `sqrt(c + d x^3)` and `q = (d/c)^(1/3)`, by the sign of `c` | +| `"x/((8 - d*x^3)*sqrt(1 + d*x^3))".ToEntity().Integrate("x")` | `integral(...)` | `(artanh((1 + d^(1/3) x)^2/(3 sqrt(1 + d x^3))) - artanh(sqrt(1 + d x^3)/3))/(18 d^(2/3)) - atan(sqrt(3) (1 + d^(1/3) x)/sqrt(1 + d x^3))/(6 sqrt(3) d^(2/3))`, written with logarithms | + ### `x^2` over a three-quarter power of a quadratic binomial beside another is integrated where that is elementary **Answers where there were none.** `x^2/((A + B x^2)^(3/4) (C + D x^2))` at `B C - 2 A D = 0`, Rubi's diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 838463566..2e96723dc 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -4920,6 +4920,105 @@ Entity OverOnePlus(Entity k) return holds ? answer : null; } + /// + /// x/((a + b x^3) sqrt(c + d x^3)) at the two ratios where its integral is elementary, + /// Rubi's 1.1.3.4: 4 b c = a d and 8 b c + a d = 0, in closed form. + /// + /// + /// + /// With q = (d/c)^(1/3), the real root, and y = sqrt(c + d x^3), at + /// 4 b c = a d, for r = sqrt(c) where c is positive and + /// r = sqrt(-c) where it is negative: + /// + /// c > 0: q/(3 2^(2/3) b r) (artanh(y/r)/3 + atan(y/(sqrt(3) r))/sqrt(3) + /// - atan(sqrt(3) r (1 + 2^(1/3) q x)/y)/sqrt(3) - artanh(r (1 - 2^(1/3) q x)/y)) + /// c < 0: -q/(3 2^(2/3) b r) (atan(y/r)/3 + artanh(y/(sqrt(3) r))/sqrt(3) + /// + artanh(sqrt(3) r (1 + 2^(1/3) q x)/y)/sqrt(3) + atan(r (1 - 2^(1/3) q x)/y)) + /// + /// and at 8 b c + a d = 0, where the integrand is -(d/b) x/((8 c - d x^3) y), that + /// times + /// + /// c > 0: (artanh(r (1 + q x)^2/(3 y))/18 - artanh(y/(3 r))/18 - atan(sqrt(3) r (1 + q x)/y)/(6 sqrt(3)))/(r^3 q^2) + /// c < 0: -(atan(r (1 + q x)^2/(3 y))/18 + atan(y/(3 r))/18 - artanh(sqrt(3) r (1 + q x)/y)/(6 sqrt(3)))/(r^3 q^2) + /// + /// Rubi splits the second into three integrals, a substitution's in x^3, a linear + /// over a linear and a quadratic over a quadratic, each elementary, and their sum is the + /// form above; asked as three, two come back piecewise in the symbols and their sum holds + /// every combination of the arms. Every condition is decided as a value, and the answer is + /// differentiated back at sampled points before it is returned. + /// Welz's x/((4 - x^3) sqrt(1 - x^3)) and x/((8 - d x^3) sqrt(1 + d x^3)) were + /// declined, with the rest of 1.1.3.4 at those ratios. + /// https://github.com/asc-community/AngouriMath/issues/718 + /// + /// + internal static Entity? SolveXOverACubicBinomialBesideTheRootOfAnother(Entity expr, Entity.Variable x, bool integrateByParts) + { + var (numerator, denominator) = Functions.SingleQuotient.Of(expr); + if (!TreeAnalyzer.TryGetPolynomial(numerator, x, out var above) || above.Count != 1 + || !above.TryGetValue(EInteger.One, out var multiple) || multiple.ContainsNode(x)) + return null; + Entity? radicand = null; + Entity below = Number.Integer.One; + foreach (var factor in Mulf.LinearChildren(denominator)) + if (factor is Powf(var @base, Number.Rational half) && half.ERational.Equals(ERational.Create(1, 2)) && @base.ContainsNode(x)) + { + if (radicand is not null) + return null; + radicand = @base; + } + else + below = below == Number.Integer.One ? factor : below * factor; + if (radicand is null || ACubicBinomial(radicand) is not var (c, d) || ACubicBinomial(below) is not var (a, b)) + return null; + static bool Zero(Entity condition) => Functions.PartialFractions.IsZeroAsAValue(condition); + if (Zero(a) || Zero(b) || Zero(c) || Zero(d) || Zero(b * c - a * d)) + return null; + var y = MathS.Sqrt(radicand); + var q = MathS.Pow(LowestOverTheSymbols(d / c), Number.Rational.Create(1, 3)); + var cubeRootOfTwo = MathS.Pow(2, Number.Rational.Create(1, 3)); + var sqrtOfThree = MathS.Sqrt(3); + Entity? answer = null; + if (Zero(4 * b * c - a * d)) + { + var r = MathS.Sqrt(c); + var wherePositive = q / (3 * MathS.Sqr(cubeRootOfTwo) * b * r) + * (MathS.Hyperbolic.Artanh(y / r) / 3 + MathS.Arctan(y / (sqrtOfThree * r)) / sqrtOfThree + - MathS.Arctan(sqrtOfThree * r * (1 + cubeRootOfTwo * q * x) / y) / sqrtOfThree + - MathS.Hyperbolic.Artanh(r * (1 - cubeRootOfTwo * q * x) / y)); + var s = MathS.Sqrt(LowestOverTheSymbols(-c)); + var whereNegative = -q / (3 * MathS.Sqr(cubeRootOfTwo) * b * s) + * (MathS.Arctan(y / s) / 3 + MathS.Hyperbolic.Artanh(y / (sqrtOfThree * s)) / sqrtOfThree + + MathS.Hyperbolic.Artanh(sqrtOfThree * s * (1 + cubeRootOfTwo * q * x) / y) / sqrtOfThree + + MathS.Arctan(s * (1 - cubeRootOfTwo * q * x) / y)); + answer = BySign(c, wherePositive, whereNegative); + } + else if (Zero(8 * b * c + a * d)) + { + // The integrand is -(d/b) x/((8 c - d x^3) y). + var r = MathS.Sqrt(c); + var wherePositive = (MathS.Hyperbolic.Artanh(r * MathS.Sqr(1 + q * x) / (3 * y)) / 18 - MathS.Hyperbolic.Artanh(y / (3 * r)) / 18 + - MathS.Arctan(sqrtOfThree * r * (1 + q * x) / y) / (6 * sqrtOfThree)) / (MathS.Pow(r, 3) * MathS.Sqr(q)); + var s = MathS.Sqrt(LowestOverTheSymbols(-c)); + var whereNegative = -(MathS.Arctan(s * MathS.Sqr(1 + q * x) / (3 * y)) / 18 + MathS.Arctan(y / (3 * s)) / 18 + - MathS.Hyperbolic.Artanh(sqrtOfThree * s * (1 + q * x) / y) / (6 * sqrtOfThree)) / (MathS.Pow(s, 3) * MathS.Sqr(q)); + answer = LowestOverTheSymbols(-d / b) * BySign(c, wherePositive, whereNegative); + } + if (answer is null) + return null; + answer = (multiple * answer).InnerSimplified; + bool holds; + using (MathS.Settings.DowncastingEnabled.Set(false)) + holds = Functions.PartialFractions.HoldsAtSampledPoints(answer.Differentiate(x), expr, x); + return holds ? answer : null; + + // `p + r x^3`, as `(p, r)`, each free of x. + (Entity, Entity)? ACubicBinomial(Entity binomial) + => TreeAnalyzer.TryGetPolynomial(binomial, x, out var read) && read.Count == 2 + && read.TryGetValue(EInteger.Zero, out var constant) && read.TryGetValue(EInteger.FromInt32(3), out var cubic) + && !constant.ContainsNode(x) && !cubic.ContainsNode(x) + ? (constant, cubic) : null; + } + /// /// A root of a quadratic binomial beside another, 1/((A + B x^2)^(1/3) (C + D x^2)) /// with B C + 3 A D = 0 or B C - 9 A D = 0, and diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs index aa236edd4..8eefb0450 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs @@ -807,6 +807,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) => // `(1 + x + sqrt(3))/((1 + x - sqrt(3)) sqrt(1 + x^3))`. Before the reduction over the // linear, which writes it as `1` and `2 sqrt(3)` over the linear, neither elementary. if ((answer = IndefiniteIntegralSolver.SolveAPseudoEllipticQuotientOverTheRootOfACubicBinomial(expr, x)) is { }) return answer; + // x over a cubic binomial beside the root of another, at the two ratios where that is + // elementary: Welz's `x/((4 - x^3) sqrt(1 - x^3))`. + if ((answer = IndefiniteIntegralSolver.SolveXOverACubicBinomialBesideTheRootOfAnother(expr, x, integrateByParts)) is { }) return answer; if ((answer = IndefiniteIntegralSolver.SolveByReducingThePolynomialOverALinearFactor(expr, x, integrateByParts)) is { }) return answer; if (expr is Entity.Sumf or Entity.Minusf && (answer = IndefiniteIntegralSolver.SolveBySplittingSum(expr, x, integrateByParts)) is { }) diff --git a/Sources/Tests/UnitTests/Calculus/XOverACubicBinomialBesideTheRootOfAnotherIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/XOverACubicBinomialBesideTheRootOfAnotherIntegralTest.cs new file mode 100644 index 000000000..9204487dd --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/XOverACubicBinomialBesideTheRootOfAnotherIntegralTest.cs @@ -0,0 +1,62 @@ +// +// 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 +{ + /// + /// x/((a + b x^3) sqrt(c + d x^3)) at the two ratios where its integral is elementary, + /// Rubi's 1.1.3.4: 4 b c = a d, in closed form by the sign of c, and + /// 8 b c + a d = 0, as three integrals. + /// #718 + /// + /// + /// Checked by differentiating back with c pinned to either sign and d = 0.7, on + /// both sides of 0 wherever the integrand is real. + /// + [Trait("Area", "Calculus")] + public sealed class XOverACubicBinomialBesideTheRootOfAnotherIntegralTest + { + private static readonly double[] Points = { -2.3, -1.4, -0.8, -0.3, 0.4, 0.9, 1.6, 2.4 }; + + [Theory] + // 4 b c = a d. + [InlineData("x/((4 - x^3)*sqrt(1 - x^3))", 1.3)] + [InlineData("x/((4 - d*x^3)*sqrt(-1 + d*x^3))", 1.3)] + [InlineData("x/((4*c + d*x^3)*sqrt(c + d*x^3))", 1.3)] + [InlineData("x/((4*c + d*x^3)*sqrt(c + d*x^3))", -1.3)] + // 8 b c + a d = 0. + [InlineData("x/((8 - d*x^3)*sqrt(1 + d*x^3))", 1.3)] + [InlineData("x/((8*c - d*x^3)*sqrt(c + d*x^3))", 1.3)] + [InlineData("x/((8*c - d*x^3)*sqrt(c + d*x^3))", -1.3)] + [InlineData("3*x/((8 + x^3)*sqrt(-1 + x^3))", 1.3)] + public void IsIntegrated(string integrand, double c) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("c", c).Substitute("d", 0.7); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in Points) + { + var want = original.Substitute("x", at).EvalNumerical(); + if (want.IsNaN || Math.Abs((double)want.ImaginaryPart) > 1e-12 * Math.Max(1, Math.Abs((double)want.RealPart))) + continue; + var got = derivative.Substitute("x", at).EvalNumerical(); + compared++; + 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}"); + } + Assert.True(compared >= 2, $"only {compared} points were real for {integrand}"); + } + } +}