Skip to content

Commit 404a98a

Browse files
Four nested roots with a closed form are integrated (#1787)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
1 parent d0b8905 commit 404a98a

4 files changed

Lines changed: 324 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,22 @@ improper fraction is declined before the first division rather than after the la
606606
| `"(1 - b*x^2)^3/(c*(1 - b*x^2) + a*d*x^2)^3".ToEntity().Integrate("x")` | no answer within a minute | the antiderivative |
607607
| `"(a + b*x)^(5/2)/(c + d*x)^4".ToEntity().Integrate("x")` | `integral(...)` | the antiderivative |
608608

609+
### Four nested roots with a closed form are integrated
610+
611+
**Answers where there were none.** Rubi's 1.3.3 gives four nested roots a closed form, and they were
612+
declined: `sqrt(a + b sqrt(c + d x^2))` with `a^2 = b^2 c`, which is
613+
`2 b^2 d x^3/(3 S^(3/2)) + 2 a x/sqrt(S)` for the root `S`; `sqrt(c x^2 + d sqrt(a + b x^4))/sqrt(a + b x^4)`
614+
with `c^2 = b d^2`, and `1/((a + b x^n) sqrt(c x^2 + d (a + b x^n)^(2/n)))`, each the derivative of
615+
`w = x/sqrt(S)` over `1 - k w^2`; and `sqrt(a x^2 + b x sqrt(c + d x^2))/(x sqrt(c + d x^2))` with
616+
`a^2 = b^2 d` and `b^2 c + a = 0`, in `t = a x + b sqrt(c + d x^2)`. Rubi's 1.3.2 and the Welz and
617+
Timofeev suites ([#718](https://github.com/asc-community/AngouriMath/issues/718)).
618+
619+
| Input | Was (2.5.0) | Now |
620+
|---|---|---|
621+
| `"sqrt(1 + sqrt(1 - x^2))".ToEntity().Integrate("x")` | `integral(...)` | `-2 x^3/(3 (1 + sqrt(1 - x^2))^(3/2)) + 2 x/sqrt(1 + sqrt(1 - x^2))` |
622+
| `"sqrt(x^2 + sqrt(1 + x^4))/sqrt(1 + x^4)".ToEntity().Integrate("x")` | `integral(...)` | a logarithm of `x/sqrt(x^2 + sqrt(1 + x^4))`, over `2 sqrt(2)` |
623+
| `"1/((1 + x^4)*sqrt(-x^2 + sqrt(1 + x^4)))".ToEntity().Integrate("x")` | `integral(...)` | `arctan(x/sqrt(-x^2 + sqrt(1 + x^4)))` |
624+
609625
### A decline is held at the depth it was made at
610626

611627
**Answers where there were none.** The integrator remembers what it has worked out, declines

‎Sources/AngouriMath/Functions/Continuous/Integration/IndefiniteIntegralSolver.cs‎

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18321,6 +18321,254 @@ private static bool IsAWholeOrHalfOddMultiple(Entity timesN)
1832118321
internal static bool IsAPerfectSquareDiscriminant(Entity a, Entity b, Entity c)
1832218322
=> VanishesIdentically(b * b - Number.Integer.Create(4) * a * c);
1832318323

18324+
/// <summary>
18325+
/// Four nested roots with a closed form, Rubi's 1.3.3: <c>sqrt(a + b sqrt(c + d x^2))</c>
18326+
/// with <c>a^2 = b^2 c</c>; <c>sqrt(c x^2 + d sqrt(a + b x^4))/sqrt(a + b x^4)</c> with
18327+
/// <c>c^2 = b d^2</c>; <c>1/((a + b x^n) sqrt(c x^2 + d (a + b x^n)^(2/n)))</c>; and
18328+
/// <c>sqrt(a x^2 + b x sqrt(c + d x^2))/(x sqrt(c + d x^2))</c> with <c>a^2 = b^2 d</c> and
18329+
/// <c>b^2 c + a = 0</c>.
18330+
/// </summary>
18331+
/// <remarks>
18332+
/// <para>
18333+
/// The first is <c>2 b^2 d x^3/(3 S^(3/2)) + 2 a x/sqrt(S)</c> for the root <c>S</c>. The
18334+
/// second and third are the derivative of <c>w = x/sqrt(S)</c> over <c>1 - 2c w^2</c> and
18335+
/// <c>1 - c w^2</c>: <c>sqrt(S)/R</c> for the inner root <c>R</c> is <c>d w'/(1 - 2c w^2)</c>, since
18336+
/// <c>S' = 2 c x S/(d R)</c> where <c>c^2 = b d^2</c>, and <c>1 - 2c w^2 = d R/S</c>. The fourth is
18337+
/// <c>sqrt(2) b/a</c> times <c>1/sqrt(1 + t^2/a)</c> in <c>t = a x + b sqrt(c + d x^2)</c>. The
18338+
/// integral in <c>w</c> or <c>t</c> is asked of the integrator with its coefficient named, and
18339+
/// the answer is differentiated back before it is returned. Rubi's 1.3.2 and the Welz and
18340+
/// Timofeev suites declined all of them.
18341+
/// https://github.com/asc-community/AngouriMath/issues/718
18342+
/// </para>
18343+
/// </remarks>
18344+
internal static Entity? SolveANestedRootByItsClosedForm(Entity expr, Entity.Variable x, bool integrateByParts)
18345+
{
18346+
// The factors with x in them as powers, those below the bar negated, and the constant
18347+
// in front: `sqrt(S)/sqrt(P)` arrives as `sqrt(S) (sqrt(P))^(-1)`.
18348+
Entity constant = Number.Integer.One;
18349+
var factors = new List<Entity>();
18350+
var (above, below) = Functions.SingleQuotient.Of(expr);
18351+
foreach (var (side, sign) in new[] { (above, 1), (below, -1) })
18352+
foreach (var factor in Mulf.LinearChildren(side))
18353+
{
18354+
if (!factor.ContainsNode(x))
18355+
{
18356+
if (factor == Number.Integer.One)
18357+
continue;
18358+
var k = sign > 0 ? factor : 1 / factor;
18359+
constant = constant == Number.Integer.One ? k : constant * k;
18360+
continue;
18361+
}
18362+
factors.Add(factor is Powf(var b, var p) && !p.ContainsNode(x)
18363+
? MathS.Pow(b, sign > 0 ? p : (-p).InnerSimplified)
18364+
: sign > 0 ? factor : MathS.Pow(factor, Number.Integer.MinusOne));
18365+
}
18366+
if (factors.Count is < 1 or > 3)
18367+
return null;
18368+
var half = Number.Rational.Create(1, 2);
18369+
var answer = factors.Count switch
18370+
{
18371+
1 => ARootOfAConstantPlusARootOfAQuadratic(factors[0]),
18372+
2 => ARootOverTheRootInsideIt(factors) ?? AReciprocalOfTheRootInsideIt(factors),
18373+
_ => AProductRootOverTheRootInsideIt(factors),
18374+
};
18375+
if (answer is null)
18376+
return null;
18377+
answer = constant == Number.Integer.One ? answer : constant * answer;
18378+
return Functions.PartialFractions.DerivativeHoldsAtSampledPoints(answer, expr, x) ? answer : null;
18379+
18380+
// sqrt(a + b sqrt(c + d x^2)), a^2 = b^2 c: 2 b^2 d x^3/(3 S^(3/2)) + 2 a x/sqrt(S).
18381+
Entity? ARootOfAConstantPlusARootOfAQuadratic(Entity factor)
18382+
{
18383+
if (factor is not Powf(var sum, var power) || power != half || ConstantPlusMultipleOfARoot(sum) is not var (a, b, inner)
18384+
|| Coefficients(inner, 0, 2) is not [var c, var d])
18385+
return null;
18386+
if (!Functions.PartialFractions.IsZeroAsAValue(a * a - b * b * c))
18387+
return null;
18388+
return 2 * b * b * d * MathS.Pow(x, 3) / (3 * MathS.Pow(sum, Number.Rational.Create(3, 2))) + 2 * a * x / MathS.Pow(sum, half);
18389+
}
18390+
18391+
// sqrt(c x^2 + d sqrt(P))/sqrt(P), P = a + b x^4, c^2 = b d^2: d G(x/sqrt(S)), G' = 1/(1 - 2c w^2).
18392+
Entity? ARootOverTheRootInsideIt(List<Entity> pair)
18393+
{
18394+
if (pair.Find(f => f is Powf(_, var p) && p == half) is not Powf(var sum, _)
18395+
|| pair.Find(f => f is Powf(_, var p) && p == -half) is not Powf(var radicand, _)
18396+
|| Coefficients(radicand, 0, 4) is not [_, var b]
18397+
|| SquareOfXPlusMultipleOfRoot(sum, radicand, half) is not var (c, d)
18398+
|| !Functions.PartialFractions.IsZeroAsAValue(c * c - b * d * d))
18399+
return null;
18400+
return InW(d, 2 * c, x / MathS.Pow(sum, half));
18401+
}
18402+
18403+
// 1/((a + b x^n) sqrt(c x^2 + d (a + b x^n)^(2/n))): (1/a) H(x/sqrt(S)), H' = 1/(1 - c w^2).
18404+
Entity? AReciprocalOfTheRootInsideIt(List<Entity> pair)
18405+
{
18406+
if (pair.Find(f => f is Powf(_, var p) && p == -half) is not Powf(var sum, _)
18407+
|| pair.Find(f => f is Powf(_, var p) && p == Number.Integer.MinusOne) is not Powf(var outer, _)
18408+
|| Sumf.LinearChildren(outer) is not { Count: 2 } terms)
18409+
return null;
18410+
var a = terms.FirstOrDefault(t => !t.ContainsNode(x));
18411+
var power = terms.FirstOrDefault(t => t.ContainsNode(x)) is { } t2 && ConstantTimesAWholePowerOfX(t2) is var (_, n) ? n : null;
18412+
if (a is null || power is null)
18413+
return null;
18414+
if (SquareOfXPlusMultipleOfRoot(sum, outer, Number.Rational.Create(2, power.EInteger)) is not var (c, _))
18415+
return null;
18416+
return InW(1 / a, c, x / MathS.Pow(sum, half));
18417+
}
18418+
18419+
// sqrt(a x^2 + b x sqrt(Q))/(x sqrt(Q)), Q = c + d x^2, a^2 = b^2 d, b^2 c + a = 0:
18420+
// sqrt(2) b/a K(a x + b sqrt(Q)), K' = 1/sqrt(1 + t^2/a).
18421+
Entity? AProductRootOverTheRootInsideIt(List<Entity> three)
18422+
{
18423+
if (three.Find(f => f is Powf(_, var p) && p == half) is not Powf(var sum, _)
18424+
|| three.Find(f => f is Powf(_, var p) && p == -half) is not Powf(var radicand, _)
18425+
|| !three.Any(f => f is Powf(var bx, var p) && bx == x && p == Number.Integer.MinusOne)
18426+
|| Coefficients(radicand, 0, 2) is not [var c, var d])
18427+
return null;
18428+
// a x^2 + b x sqrt(Q), as two terms.
18429+
Entity? a = null, b = null;
18430+
foreach (var term in Sumf.LinearChildren(sum))
18431+
{
18432+
if (ConstantTimesAWholePowerOfX(term) is var (k, two) && two.EInteger.Equals(EInteger.FromInt32(2)))
18433+
a = k;
18434+
else if (MultipleOf(term, MathS.Pow(x, 1) * MathS.Pow(radicand, half)) is { } m)
18435+
b = m;
18436+
else
18437+
return null;
18438+
}
18439+
if (a is null || b is null || !Functions.PartialFractions.IsZeroAsAValue(a * a - b * b * d)
18440+
|| !Functions.PartialFractions.IsZeroAsAValue(b * b * c + a))
18441+
return null;
18442+
var t = Variable.CreateUnique(expr, "t_nested");
18443+
var reciprocal = Lowest(1 / a);
18444+
Entity q = reciprocal.Vars.Any() ? Variable.CreateUnique(expr, "q_nested") : reciprocal;
18445+
if (Integration.ComputeIndefiniteIntegral(1 / MathS.Sqrt(1 + MathS.Pow(t, 2) * q), t, integrateByParts) is not { } inT)
18446+
return null;
18447+
var inX = inT.Substitute(t, a * x + b * MathS.Pow(radicand, half));
18448+
return MathS.Sqrt(2) * b / a * (q is Variable named ? inX.Substitute(named, reciprocal) : inX);
18449+
}
18450+
18451+
// multiple times the integral of 1/(1 - q w^2) at w = at, with q named while it is asked.
18452+
Entity? InW(Entity multiple, Entity coefficient, Entity at)
18453+
{
18454+
// Named only where it has symbols in it: a number put in afterwards is not folded,
18455+
// and `sqrt(4 q)` at `q = 2` was written `2/1 * 2^(1/2)`.
18456+
var w = Variable.CreateUnique(expr, "w_nested");
18457+
var lowest = Lowest(coefficient);
18458+
Entity q = lowest.Vars.Any() ? Variable.CreateUnique(expr, "q_nested") : lowest;
18459+
if (Integration.ComputeIndefiniteIntegral(1 / (1 - q * MathS.Pow(w, 2)), w, integrateByParts) is not { } inW)
18460+
return null;
18461+
var written = inW.Substitute(w, at);
18462+
return Lowest(multiple) * (q is Variable named ? written.Substitute(named, lowest) : written);
18463+
}
18464+
18465+
// A constant in lowest terms over its symbols, and a number as the number it is: the
18466+
// lowest terms of `2` are `2/1`.
18467+
static Entity Lowest(Entity constant)
18468+
=> constant.Vars.Any() ? Functions.PartialFractions.InLowestTermsOverTheSymbols(constant) : constant.InnerSimplified;
18469+
18470+
// c x^2 + d R^power, with R as given: (c, d).
18471+
(Entity C, Entity D)? SquareOfXPlusMultipleOfRoot(Entity sum, Entity root, Entity power)
18472+
{
18473+
Entity? c = null, d = null;
18474+
foreach (var term in Sumf.LinearChildren(sum))
18475+
{
18476+
if (ConstantTimesAWholePowerOfX(term) is var (k, two) && two.EInteger.Equals(EInteger.FromInt32(2)))
18477+
c = c is null ? k : null;
18478+
else if (MultipleOf(term, MathS.Pow(root, power)) is { } m)
18479+
d = d is null ? m : null;
18480+
else
18481+
return null;
18482+
}
18483+
return c is null || d is null ? null : (c, d);
18484+
}
18485+
18486+
// a + b sqrt(Q), with Q holding x: (a, b, Q).
18487+
(Entity A, Entity B, Entity Inner)? ConstantPlusMultipleOfARoot(Entity sum)
18488+
{
18489+
Entity? a = null, b = null, inner = null;
18490+
foreach (var term in Sumf.LinearChildren(sum))
18491+
{
18492+
if (!term.ContainsNode(x))
18493+
{
18494+
a = a is null ? term : a + term;
18495+
continue;
18496+
}
18497+
if (inner is not null)
18498+
return null;
18499+
Entity k = Number.Integer.One;
18500+
foreach (var part in Mulf.LinearChildren(term))
18501+
if (!part.ContainsNode(x))
18502+
k = k == Number.Integer.One ? part : k * part;
18503+
else if (inner is null && part is Powf(var r, var p) && p == half)
18504+
inner = r;
18505+
else
18506+
return null;
18507+
b = k;
18508+
}
18509+
return a is null || b is null || inner is null ? null : (a, b, inner);
18510+
}
18511+
18512+
// term = m times the given product of factors with x, m free of x.
18513+
Entity? MultipleOf(Entity term, Entity product)
18514+
{
18515+
var wanted = Mulf.LinearChildren(product).Where(f => f != Number.Integer.One).ToList();
18516+
Entity m = Number.Integer.One;
18517+
foreach (var part in Mulf.LinearChildren(term))
18518+
{
18519+
if (!part.ContainsNode(x))
18520+
{
18521+
m = m == Number.Integer.One ? part : m * part;
18522+
continue;
18523+
}
18524+
var at = wanted.FindIndex(w => w == part || (part == x && w is Powf(var wb, var wp) && wb == x && wp == Number.Integer.One));
18525+
if (at < 0)
18526+
return null;
18527+
wanted.RemoveAt(at);
18528+
}
18529+
return wanted.Count == 0 ? m : null;
18530+
}
18531+
18532+
// c x^n with n whole: (c, n); a constant is n = 0.
18533+
(Entity Coefficient, Number.Integer Power)? ConstantTimesAWholePowerOfX(Entity term)
18534+
{
18535+
Entity k = Number.Integer.One;
18536+
Number.Integer? n = null;
18537+
foreach (var part in Mulf.LinearChildren(term))
18538+
{
18539+
if (!part.ContainsNode(x))
18540+
{
18541+
k = k == Number.Integer.One ? part : k * part;
18542+
continue;
18543+
}
18544+
if (n is not null)
18545+
return null;
18546+
n = part == x ? Number.Integer.One : part is Powf(var bx, Number.Integer e) && bx == x ? e : null;
18547+
if (n is null)
18548+
return null;
18549+
}
18550+
return (k, n ?? Number.Integer.Zero);
18551+
}
18552+
18553+
// The coefficients of a polynomial in x with terms of the two degrees given and no other.
18554+
Entity[]? Coefficients(Entity polynomial, int low, int high)
18555+
{
18556+
Entity? lowCoefficient = null, highCoefficient = null;
18557+
foreach (var term in Sumf.LinearChildren(polynomial))
18558+
{
18559+
if (ConstantTimesAWholePowerOfX(term) is not var (k, n))
18560+
return null;
18561+
if (n.EInteger.Equals(EInteger.FromInt32(low)))
18562+
lowCoefficient = lowCoefficient is null ? k : lowCoefficient + k;
18563+
else if (n.EInteger.Equals(EInteger.FromInt32(high)))
18564+
highCoefficient = highCoefficient is null ? k : highCoefficient + k;
18565+
else
18566+
return null;
18567+
}
18568+
return lowCoefficient is null || highCoefficient is null ? null : new[] { lowCoefficient, highCoefficient };
18569+
}
18570+
}
18571+
1832418572
/// <summary>
1832518573
/// A square root of a perfect square in <paramref name="x"/> is the modulus:
1832618574
/// <c>sqrt(x^2)</c> is <c>|x|</c>, which for a real <c>x</c> is <c>sgn(x) x</c>, and

‎Sources/AngouriMath/Functions/Continuous/Integration/Integration.Definition.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
706706
if ((answer = IndefiniteIntegralSolver.SolveByTakingARootOfAPerfectSquare(expr, x, integrateByParts)) is { }) return answer;
707707
// And a square in one trigonometric function, at the top: the modulus of the linear in it.
708708
if ((answer = IndefiniteIntegralSolver.SolveByTakingARootOfAPerfectSquareInATrigonometricFunction(expr, x, integrateByParts)) is { }) return answer;
709+
// Four nested roots with a closed form: `sqrt(1 + sqrt(1 - x^2))`,
710+
// `sqrt(x^2 + sqrt(1 + x^4))/sqrt(1 + x^4)` and the two beside them in Rubi's 1.3.3.
711+
if ((answer = IndefiniteIntegralSolver.SolveANestedRootByItsClosedForm(expr, x, integrateByParts)) is { }) return answer;
709712
// And any power of a square in any power of x, as the power of its root times a factor
710713
// constant where the root is not zero: `x^2 (a^2 + 2 a b x^3 + b^2 x^6)^p`.
711714
if ((answer = IndefiniteIntegralSolver.SolveByWritingAPowerOfASquareAsAPowerOfItsRoot(expr, x, integrateByParts)) is { }) return answer;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// Copyright (c) 2019-2026 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using System;
9+
using AngouriMath.Extensions;
10+
using Xunit;
11+
12+
namespace AngouriMath.Tests.Calculus
13+
{
14+
/// <summary>
15+
/// Four nested roots with a closed form, Rubi's 1.3.3: a root of a constant plus a root of a
16+
/// quadratic, a root over the root of the quartic inside it, a reciprocal of that quartic
17+
/// beside the root, and a root over <c>x</c> and the root of the quadratic inside it.
18+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
19+
/// </summary>
20+
/// <remarks>
21+
/// Checked by differentiating back with <c>a = 0.7</c>, <c>b = 1.3</c>, <c>c = 0.6</c> and
22+
/// <c>d = 1.1</c>, on both sides of 0 where the integrand is real there.
23+
/// </remarks>
24+
[Trait("Area", "Calculus")]
25+
public sealed class NestedRootClosedFormIntegralTest
26+
{
27+
[Theory]
28+
[InlineData("sqrt(1 + sqrt(1 - x^2))")]
29+
[InlineData("sqrt(a + b*sqrt(a^2/b^2 + c*x^2))")]
30+
[InlineData("sqrt(x^2 + sqrt(1 + x^4))/sqrt(1 + x^4)")]
31+
[InlineData("sqrt(-b*x^2 + sqrt(a + b^2*x^4))/sqrt(a + b^2*x^4)")]
32+
[InlineData("1/((1 + x^4)*sqrt(-x^2 + sqrt(1 + x^4)))")]
33+
[InlineData("1/((a + b*x^4)*sqrt(c*x^2 + d*sqrt(a + b*x^4)))")]
34+
[InlineData("sqrt(a*x^2 + b*x*sqrt(-a/b^2 + a^2*x^2/b^2))/(x*sqrt(-a/b^2 + a^2*x^2/b^2))")]
35+
public void IsIntegrated(string integrand)
36+
{
37+
var integral = integrand.ToEntity().Integrate("x");
38+
Assert.DoesNotContain("integral(", integral.Stringize());
39+
Entity Pinned(Entity e) => e.Substitute("a", 0.7).Substitute("b", 1.3).Substitute("c", 0.6).Substitute("d", 1.1);
40+
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
41+
var original = Pinned(integrand.ToEntity());
42+
var compared = 0;
43+
foreach (var at in new[] { -0.9, -0.4, 0.3, 0.8 })
44+
{
45+
var want = original.Substitute("x", at).EvalNumerical();
46+
var got = derivative.Substitute("x", at).EvalNumerical();
47+
if (want.IsNaN || got.IsNaN)
48+
continue;
49+
compared++;
50+
Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
51+
< 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart) + Math.Abs((double)want.ImaginaryPart)),
52+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
53+
}
54+
Assert.True(compared >= 2, $"only {compared} points were comparable for {integrand}");
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)