From b51e3148c6737253e22ae6249a4816f7a0f4220f Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 07:56:09 +0000 Subject: [PATCH] A whole power of a sum of two square roots below the bar is rationalised 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. A whole power is multiplied by the same power of the conjugate, expanded; an even power over a constant difference of the radicands is left as it was, since its conjugate brings the root of the product of the radicands and those squares were answered as written. Rubi's 1.3.2. Part of #718. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 15 ++++++ .../Integration/IndefiniteIntegralSolver.cs | 18 +++++++- .../APowerOfASumOfRootsIntegralTest.cs | 46 +++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/APowerOfASumOfRootsIntegralTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index e9ae2c6a1..69c3a8809 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -178,6 +178,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 9965d1608..ec04be3ff 100644 --- a/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs @@ -19174,19 +19174,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}"); + } + } + } +}