diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 706174fdb..c2f6fee8c 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -6216,6 +6216,24 @@ with `K = sqrt(L1) sqrt(L2)/sqrt(M)` in front of the answer, constant wherever i Rubi's 7.1.4 and 7.1.5, all 376 problems that count: 308 to 345, no row lost, 56 timeouts to 27. +### Half-odd powers of the two linears whose product is the radicand `arsinh` holds are paired to leave a polynomial + +**Answers where there were none.** `(a + b arsinh(c x)) sqrt(f - i c f x)/(d + i c d x)^(3/2)`, +`(d + i c d x)^(3/2) (f - i c f x)^(5/2) (a + b arsinh(c x))` and ten more of Rubi's 7.1.4 were declined. With both +linears above the bar the smaller power is now the one paired into the radicand `M = 1 + c^2 x^2`, so that the whole +power left is above the bar too: `(d + i c d x)^(3/2) (f - i c f x)^(5/2)` is `K^3 (f - i c f x) M^(3/2)`, a polynomial +beside the root. With the second below the bar under a power of `-3/2` or less, the whole power of the first is +written out as monomials, each over the root, and each `x^k (a + b arsinh(c x))/M^(3/2)` is answered alone. Over +`sqrt(M)`, and with both linears below the bar, the pairing is as before +([#233](https://github.com/asc-community/AngouriMath/issues/233)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"(a+b*asinh(c*x))*sqrt(f-i*c*f*x)/(d+i*c*d*x)^(3/2)".ToEntity().Integrate("x")` | `integral(...)` | 1,001 characters | +| `"(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(5/2)".ToEntity().Integrate("x")` | `integral(...)` | 7,148 characters | +| `"(d+i*c*d*x)^(3/2)*(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))".ToEntity().Integrate("x")` | `integral(...)` | 3,624 characters | +| `"(d+i*c*d*x)^(3/2)*(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))^2".ToEntity().Integrate("x")` | `integral(...)` | 7,625 characters | + ### The radicand an inverse sine or cosine holds is read as one `(d + c d x)^(1/2) (f - c f x)^(3/2) (a + b arcsin(c x))` ran out of time. `arcsin(u)` holds a diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index b64fa6967..d8c92eefa 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -31898,6 +31898,26 @@ Piecewise piecewise when piecewise.Cases.Any() => piecewise.Cases.First().Expres /// rows this way; `(d + i c d x)^(5/2) sqrt(f - i c f x) (a + b arsinh(c x))` ran past /// seventy seconds where the same thing over `1 + c^2 x^2` takes two thirds of one. /// + /// + /// + /// Both factors above the bar, `q` is the smaller power, so that the whole power left is + /// above the bar too: `(d + i c d x)^(3/2) (f - i c f x)^(5/2)` is `K^3 (f - i c f x) M^(3/2)`, + /// a polynomial beside the root. Paired the other way it was `K^5 M^(5/2)/(d + i c d x)`, + /// which ran past the budget, as did the same with `(a + b arsinh(c x))^2`; the polynomial + /// takes half a second. + /// + /// + /// The first factor above the bar and the second below it under a power of `-3/2` or + /// less, the whole power `L1^n` is written out as a sum of monomials, each over the root: + /// `sqrt(f - i c f x)/(d + i c d x)^(3/2)` is `K^(-3) (f - i c f x)^2/M^(3/2)`, and as that + /// product the rules for a power of a quadratic do not read it -- ten of Rubi's 7.1.4 + /// rows, from `(f - i c f x)^2` to `(f - i c f x)^5` over `M^(3/2)` and `M^(5/2)`, ran past + /// the budget -- while each `x^k (a + b arsinh(c x))/M^(3/2)` is answered in a tenth of a + /// second. Over `sqrt(M)` the product is answered as it is, and is left so; and both + /// factors below the bar are left as they are paired. + /// https://github.com/asc-community/AngouriMath/issues/233 + /// + /// private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Variable x, List references, Entity.Variable multiple, out Entity rewritten, out Entity? root) { @@ -31928,6 +31948,11 @@ private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Vari var twice = (2 * half).Evaled; if (half is not Number.Integer && twice is not Number.Integer) continue; + // Both above the bar, the smaller power is the one paired, so that the whole + // power left over is above it too. See the remarks. + var whole = (Number.Integer)(p - q).Evaled; + if (half is not Number.Integer && !half.IsNegative && whole.IsNegative) + continue; var product = (first * second).Expand(); foreach (var candidate in references) { @@ -31944,6 +31969,14 @@ private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Vari together = MathS.Pow(multiple, twice) * MathS.Pow(candidate, half); root = MathS.Sqrt(first) * MathS.Sqrt(second) / MathS.Sqrt(candidate); } + // The first factor above the bar and the root of the radicand 3/2 or more + // below it: a sum of monomials over that root. See the remarks. + if (half is not Number.Integer && half < Number.Rational.Create(-1, 1) && p.Evaled is Number.Real { IsPositive: true } + && AsMonomialsOverARoot(first, whole, x, factors.Where((_, k) => k != i && k != j), candidate, (Number.Rational)(-half)) is { } monomials) + { + rewritten = MathS.Pow(multiple, twice) * monomials; + return true; + } Entity result = MathS.Pow(first, (p - q).InnerSimplified) * together; for (var k = 0; k < factors.Count; k++) if (k != i && k != j) @@ -31961,6 +31994,44 @@ private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Vari Powf(var @base, var exponent) => (@base, exponent), _ => (factor, Number.Integer.One), }; + + // `L^n g/M^r` as `sum_k a_k x^k g/M^r`, each term spelled as a quotient, which is how + // the rules for a power of a quadratic read it; null where L^n is no polynomial. + static Entity? AsMonomialsOverARoot(Entity linear, Number.Integer power, Entity.Variable x, + IEnumerable<(Entity Base, Entity Exponent)> rest, Entity radicand, Number.Rational rootPower) + { + if (!TreeAnalyzer.TryGetPolynomial(MathS.Pow(linear, power), x, out var terms) + || terms.Keys.Any(key => key.Sign < 0) || terms.Values.Any(coefficient => coefficient.ContainsNode(x))) + return null; + // The other factors, a negative power of one below the bar with the radicand's root. + Entity? beside = null; + Entity under = MathS.Pow(radicand, rootPower); + foreach (var (@base, exponent) in rest) + { + if (exponent.Evaled is Number.Real { IsNegative: true } negative) + { + var positive = (-negative).Evaled; + under = under * (positive == Number.Integer.One ? @base : MathS.Pow(@base, positive)); + continue; + } + var factor = exponent == Number.Integer.One ? @base : MathS.Pow(@base, exponent); + beside = beside is null ? factor : beside * factor; + } + Entity? sum = null; + foreach (var pair in terms.OrderBy(term => term.Key)) + { + var degree = pair.Key; + var written = pair.Value.InnerSimplified; + if (VanishesIdentically(written)) + continue; + Entity term = degree.IsZero ? written : written * (degree.Equals(EInteger.One) ? x : MathS.Pow(x, Number.Integer.Create(degree))); + if (beside is not null) + term = term * beside; + term = term / under; + sum = sum is null ? term : sum + term; + } + return sum; + } } /// Set while asks its product. diff --git a/Sources/Tests/UnitTests/Calculus/APairOfLinearsOverARadicandAsMonomialsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APairOfLinearsOverARadicandAsMonomialsIntegralTest.cs new file mode 100644 index 000000000..d0d4a1934 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APairOfLinearsOverARadicandAsMonomialsIntegralTest.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 +{ + /// + /// Two linears whose product is a multiple of the radicand of arsinh(c x), + /// (d + i c d x)(f - i c f x) = d f (1 + c^2 x^2), under powers a whole number apart: + /// with the root of the radicand 3/2 or more below the bar the whole power above it is + /// written out as monomials over the root, and with both above the bar the smaller power is + /// paired. Each ran past the budget. Rubi's 7.1.4. + /// #233 + /// + [Trait("Area", "Calculus")] + public sealed class APairOfLinearsOverARadicandAsMonomialsIntegralTest + { + [Theory] + [InlineData("(a+b*asinh(c*x))*sqrt(f-i*c*f*x)/(d+i*c*d*x)^(3/2)")] + [InlineData("(f-i*c*f*x)^(3/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(3/2)")] + [InlineData("(f-i*c*f*x)^(3/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(5/2)")] + [InlineData("(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(3/2)")] + [InlineData("(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))/(d+i*c*d*x)^(5/2)")] + [InlineData("(d+i*c*d*x)^(5/2)*(a+b*asinh(c*x))/(f-i*c*f*x)^(3/2)")] + [InlineData("(d+i*c*d*x)^(3/2)*(a+b*asinh(c*x))/(f-i*c*f*x)^(3/2)")] + [InlineData("(a+b*asinh(c*x))*sqrt(d+i*c*d*x)/(f-i*c*f*x)^(3/2)")] + [InlineData("(d+i*c*d*x)^(5/2)*(a+b*asinh(c*x))/(f-i*c*f*x)^(5/2)")] + [InlineData("(d+i*c*d*x)^(3/2)*(a+b*asinh(c*x))/(f-i*c*f*x)^(5/2)")] + [InlineData("(d+i*c*d*x)^(3/2)*(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))")] + [InlineData("(d+i*c*d*x)^(3/2)*(f-i*c*f*x)^(5/2)*(a+b*asinh(c*x))^2")] + public void AnsweredAndDifferentiatesBack(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + var text = integral.Stringize(); + Assert.DoesNotContain("integral(", text); + Entity Pinned(Entity e) => e.Substitute("a", 0.9).Substitute("b", 1.7).Substitute("c", 0.6).Substitute("d", -1.3).Substitute("f", 0.8); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + var compared = 0; + foreach (var at in new[] { -1.1, -0.4, 0.3, 0.7, 1.2, 2.1 }) + { + var want = original.Substitute("x", at).EvalNumerical(); + var got = derivative.Substitute("x", at).EvalNumerical(); + if (want.IsNaN) + continue; + compared++; + 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-9, $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}"); + } + Assert.True(compared >= 5, $"only {compared} points could be compared for {integrand}"); + } + } +}