diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index dc39a1fe6..c29045763 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -195,6 +195,21 @@ answer on the far side of a root gets a real expression where it used to get a c | `1/(x*(-4+x^2)^4)` | 8,472 ms | 1,878 ms | | `1/((1+x)^3*(2+x)^3)` | 1,343 ms | 492 ms | +### A whole power of a sum of two square roots below the bar is rationalised + +**Answers where there were none.** `x/(sqrt(a + b x) + sqrt(c + b x))^3` was declined, while the +first power of such a sum below the bar is multiplied above and below by its conjugate, the product +of the pair being the difference of the radicands. A whole power is multiplied by the same power of +the conjugate now; an even power over a constant difference is left as it was, since its conjugate +brings the root of the product of the radicands with it, and those were answered as written. Rubi's +1.3.2 ([#718](https://github.com/asc-community/AngouriMath/issues/718)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"x/(sqrt(a + b*x) + sqrt(c + b*x))^3".ToEntity().Integrate("x")` | `integral(...)` | powers of the two roots over `(a - c)^3` | +| `"1/(sqrt(a + b*x) + sqrt(a + c*x))^3".ToEntity().Integrate("x")` | `integral(...)` | the same, with logarithms and arctangents | +| `"x^3/(sqrt(a + b*x) + sqrt(a + c*x))^2".ToEntity().Integrate("x")` | `integral(...)` | the same; 10 s on the unreleased master, a tenth of one now | + ### Two square roots of linears with one slope are rationalised together `sqrt(L1)` and `sqrt(L2)` with `L1 - L2` a constant are rational in their sum diff --git a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs index 875a31f1c..702c6b186 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -20255,19 +20255,33 @@ private static Entity OverTheFunctionLessARoot(Entity root, bool ofTheSine, Enti Entity rest = Number.Integer.One; foreach (var factor in Mulf.LinearChildren(denominator)) { - if (conjugate is null && factor is Sumf or Minusf && Sumf.LinearChildren(factor).ToList() is { Count: 2 } terms + // Or a whole power of the pair, whose conjugate is the power of the pair's: + // `x/(sqrt(a + b x) + sqrt(c + b x))^3` was declined where its square was answered. + var (pair, power) = factor is Powf(var raised, Number.Integer { EInteger: var whole }) && whole.Sign > 0 && whole.CompareTo(EInteger.FromInt32(8)) <= 0 + ? (raised, whole.ToInt32Checked()) + : (factor, 1); + if (conjugate is null && pair is Sumf or Minusf && Sumf.LinearChildren(pair).ToList() is { Count: 2 } terms && terms.All(term => IsARootOfAPolynomial(term))) { // The conjugate flips the sign of the second term; the product is the // difference of the squares, each square a polynomial. var first = terms[0]; var second = terms[1]; - conjugate = first - second; + conjugate = power == 1 ? first - second : MathS.Pow(first - second, power).Expand(); product = (SquareOf(first) - SquareOf(second)).InnerSimplified; // Not the same radicand twice: the difference is zero, the factor is zero // or a multiple of one root, and neither is this rule's. if (product.Evaled is Number.Complex { IsZero: true } || product.Simplify().Evaled is Number.Complex { IsZero: true }) return null; + // An even power of the conjugate has the root of the product of the radicands + // in it, `2 sqrt(A B)` in the square, which over a constant difference leaves a + // root of a quadratic where the power as written was answered: the squares over + // `sqrt(a + b x) + sqrt(c + b x)` ran past the budget so, and were answered in a + // second as they stood. + if (power % 2 == 0 && !product.Simplify().ContainsNode(x)) + return null; + if (power != 1) + product = MathS.Pow(product, power); continue; } rest = rest * factor; diff --git a/Sources/Tests/UnitTests/Calculus/APowerOfASumOfRootsIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/APowerOfASumOfRootsIntegralTest.cs new file mode 100644 index 000000000..5fa209a85 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/APowerOfASumOfRootsIntegralTest.cs @@ -0,0 +1,46 @@ +// +// 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 whole power of a sum of two square roots below the bar, multiplied above and below by the + /// same power of the conjugate: x/(sqrt(a + b x) + sqrt(c + b x))^3 is + /// x (sqrt(a + b x) - sqrt(c + b x))^3/(a - c)^3. The first power was rationalised and + /// the cubes were declined. Rubi's 1.3.2. + /// #718 + /// + [Trait("Area", "Calculus")] + public sealed class APowerOfASumOfRootsIntegralTest + { + [Theory] + [InlineData("x/(sqrt(a + b*x) + sqrt(c + b*x))^3")] + [InlineData("1/(sqrt(a + b*x) + sqrt(a + c*x))^3")] + [InlineData("x^2/(sqrt(a + b*x) + sqrt(a + c*x))^3")] + [InlineData("x^3/(sqrt(a + b*x) + sqrt(a + c*x))^2")] + public void ThroughTheConjugate(string integrand) + { + var integral = integrand.ToEntity().Integrate("x"); + Assert.DoesNotContain("integral(", integral.Stringize()); + Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.7).Substitute("c", 0.4); + var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x"); + var original = Pinned(integrand.ToEntity()); + foreach (var at in new[] { 0.3, 0.8, 1.5, 2.4 }) + { + var got = derivative.Substitute("x", at).EvalNumerical(); + var want = original.Substitute("x", at).EvalNumerical(); + 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}"); + } + } + } +}