Skip to content

Commit f68bebd

Browse files
Clear a sum with the root of a quadratic below the bar by its conjugate (#1873)
* Clear a sum with the root of a quadratic below the bar by its conjugate (A + B sqrt(Q))/(C + D sqrt(Q)) is a rational function plus one beside the root over C^2 - D^2 Q, each asked of the chain. Rubi 1.3.2:750-752 and 1182. Part of #718 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura * Record the conjugate's new answers in BREAKING-CHANGES.md Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent ed821bf commit f68bebd

4 files changed

Lines changed: 176 additions & 0 deletions

File tree

‎BREAKING-CHANGES.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,25 @@ secant too now, and the integrand is `sgn(cos(x)) (1 + u^2)^(3/2)/(a + b u)^2` i
10581058
| `"sec(x)^7/(a + b*tan(x))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,253 characters |
10591059
| `"sec(x)/(a + b*cot(x))".ToEntity().Integrate("x")` | `integral(...)` | 571 characters |
10601060

1061+
### A sum with the root of a quadratic below the bar is cleared by its conjugate
1062+
1063+
**Answers where there were none.** `1/(a c + b c x^2 + d sqrt(a + b x^2))` was declined: Euler's substitution
1064+
writes `sqrt(b)` into every coefficient for a symbolic leading coefficient, and the rational integrator could not
1065+
finish. `(A + B sqrt(Q))/(C + D sqrt(Q))` is `(A + B sqrt(Q)) (C - D sqrt(Q))/(C^2 - D^2 Q)`, a rational function and
1066+
one beside the root, and each is answered
1067+
([#718](https://github.com/asc-community/AngouriMath/issues/718)).
1068+
1069+
The symbolic answers are large because each piece's answer is piecewise in the signs of the constants, and adding
1070+
two of them multiplies their cases.
1071+
1072+
| Input | Was (2.5.0) | Now |
1073+
|---|---|---|
1074+
| `"1/(a*c + b*c*x^2 + d*sqrt(a + b*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 3,464 characters |
1075+
| `"x^2/(a*c + b*c*x^2 + d*sqrt(a + b*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 1,605 characters |
1076+
| `"1/(x^2*(a*c + b*c*x^2 + d*sqrt(a + b*x^2)))".ToEntity().Integrate("x")` | `integral(...)` | 5,052 characters |
1077+
| `"1/(c*sqrt(a + b*x^2) + d)".ToEntity().Integrate("x")` | `integral(...)` | 2,888 characters |
1078+
| `"x*sqrt(2 - x^2)/(x - sqrt(2 - x^2))".ToEntity().Integrate("x")` | `integral(...)` | 195 characters |
1079+
10611080
### A quadratic with rational roots takes Euler's third substitution at one of them
10621081

10631082
**Answers where there were none.** `1/(x + sqrt(3 - 2x - x^2))` was declined. Its leading coefficient is negative and

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

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18955,6 +18955,82 @@ node is Powf(var @base, Number.Rational r) && byRadicand.TryGetValue(@base, out
1895518955
return (rewritten, ratios);
1895618956
}
1895718957

18958+
/// <summary>
18959+
/// A rational function of <c>x</c> and one square root of a quadratic <c>Q</c>, with the
18960+
/// root in a sum below the bar, written over a denominator free of it:
18961+
/// <c>(A + B sqrt(Q))/(C + D sqrt(Q))</c> is <c>(A + B sqrt(Q)) (C - D sqrt(Q))/(C^2 - D^2 Q)</c>,
18962+
/// a rational function and one beside the root, each asked of the chain.
18963+
/// </summary>
18964+
/// <remarks>
18965+
/// Rubi's <c>1/(a c + b c x^2 + d sqrt(a + b x^2))</c>: Euler's substitution takes the
18966+
/// generic first substitution for a symbolic leading coefficient, with <c>sqrt(b)</c> in
18967+
/// every coefficient, and was declined. Over the conjugate it is
18968+
/// <c>(c Q - d sqrt(Q))/(c^2 Q^2 - d^2 Q)</c>, and both pieces are answered. It runs after
18969+
/// every rule that answers the numeric spellings. Only a sum: a root alone below the bar,
18970+
/// <c>C = 0</c>, is the root moved above it, which the chain writes back, and <c>D = 0</c>
18971+
/// has nothing to clear.
18972+
/// https://github.com/asc-community/AngouriMath/issues/718
18973+
/// </remarks>
18974+
internal static Entity? SolveByTheConjugateOfASumWithARootOfAQuadraticBelowTheBar(Entity expr, Entity.Variable x, bool integrateByParts)
18975+
{
18976+
Entity? radicand = null;
18977+
foreach (var node in expr.Nodes)
18978+
if (node is Powf(var @base, Number.Rational r) && r is not Number.Integer && @base.ContainsNode(x))
18979+
{
18980+
if (!r.ERational.Denominator.Equals(EInteger.FromInt32(2)) || !r.ERational.Numerator.CanFitInInt32()
18981+
|| radicand is not null && radicand != @base)
18982+
return null;
18983+
radicand = @base;
18984+
}
18985+
if (radicand is null || !TreeAnalyzer.TryGetPolyQuadratic(radicand, x, out var a, out _, out _) || VanishesIdentically(a))
18986+
return null;
18987+
var w = Variable.CreateUnique(expr, "w_root");
18988+
var inW = expr.Replace(node => node is Powf(var @base, Number.Rational r) && r is not Number.Integer && @base == radicand
18989+
? MathS.Pow(w, Number.Integer.Create(r.ERational.Numerator))
18990+
: node);
18991+
var (top, bottom) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(inW));
18992+
if (!bottom.ContainsNode(w) || InTheRoot(top) is not var (above, aboveBeside) || InTheRoot(bottom) is not var (below, belowBeside)
18993+
|| VanishesIdentically(below) || VanishesIdentically(belowBeside))
18994+
return null;
18995+
var over = (below * below - belowBeside * belowBeside * radicand).InnerSimplified;
18996+
if (VanishesIdentically(over))
18997+
return null;
18998+
var rational = (above * below - aboveBeside * belowBeside * radicand).InnerSimplified;
18999+
var beside = (aboveBeside * below - above * belowBeside).InnerSimplified;
19000+
Entity answer = Number.Integer.Zero;
19001+
if (!VanishesIdentically(rational))
19002+
{
19003+
if (Integration.ComputeIndefiniteIntegral(rational / over, x, integrateByParts) is not { } first)
19004+
return null;
19005+
answer += first;
19006+
}
19007+
if (!VanishesIdentically(beside))
19008+
{
19009+
if (Integration.ComputeIndefiniteIntegral(beside * MathS.Sqrt(radicand) / over, x, integrateByParts) is not { } second)
19010+
return null;
19011+
answer += second;
19012+
}
19013+
return answer;
19014+
19015+
// A polynomial in w as A + B w, with w^2 = Q.
19016+
(Entity Whole, Entity Beside)? InTheRoot(Entity side)
19017+
{
19018+
if (!TreeAnalyzer.TryGetPolynomial(side.Expand().InnerSimplified, w, out var monomials) || monomials.Keys.Any(k => k.Sign < 0))
19019+
return null;
19020+
Entity whole = Number.Integer.Zero;
19021+
Entity besideTheRoot = Number.Integer.Zero;
19022+
foreach (var pair in monomials)
19023+
{
19024+
// Q^0 written as a power is `1 provided not Q = 0`, a condition the pieces would carry.
19025+
var k = pair.Key.ToInt32Checked();
19026+
var term = k / 2 == 0 ? pair.Value : pair.Value * MathS.Pow(radicand, k / 2);
19027+
if (k % 2 == 0) whole += term;
19028+
else besideTheRoot += term;
19029+
}
19030+
return (whole.InnerSimplified, besideTheRoot.InnerSimplified);
19031+
}
19032+
}
19033+
1895819034
/// <summary>
1895919035
/// A rational function of <c>x</c> and one square root of a quadratic in <c>x</c>,
1896019036
/// rationalised by an Euler substitution and handed to the rational integrator.

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
11971197
// `A - B y` whose A/B agrees with a branch of y at the poles, found by an ansatz
11981198
// over Padé approximants.
11991199
if ((answer = SquareRootLogarithmAnsatz.Integrate(expr, x)) is { }) return answer;
1200+
// And a sum with the root below the bar, over its conjugate: after every rule above,
1201+
// which answer the numeric spellings, `1/(a c + b c x^2 + d sqrt(a + b x^2))` among them.
1202+
if ((answer = IndefiniteIntegralSolver.SolveByTheConjugateOfASumWithARootOfAQuadraticBelowTheBar(expr, x, integrateByParts)) is { }) return answer;
12001203
// Product-to-sum among the rewrites rather than before them, because a product of
12011204
// trigonometric functions of *equal* arguments is a power and wants a different tool;
12021205
// this only fires where the arguments differ, which is exactly what every substitution
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
/// A sum with the square root of a quadratic below the bar, cleared by its conjugate.
16+
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
17+
/// </summary>
18+
/// <remarks>
19+
/// Rubi's <c>1/(a c + b c x^2 + d sqrt(a + b x^2))</c> and its <c>x^2</c> and <c>1/x^2</c>
20+
/// neighbours had no antiderivative: Euler's substitution declines a symbolic leading
21+
/// coefficient. Each answer is differentiated back with the constants pinned on both signs of
22+
/// <c>b</c> and of <c>a c^2 - d^2</c>, at points where the root is real.
23+
/// </remarks>
24+
[Trait("Area", "Calculus")]
25+
public sealed class TheConjugateOfASumWithARootBelowTheBarIntegralTest
26+
{
27+
private static void DifferentiatesBack(string integrand, double[] points, params (string, double)[] pins)
28+
{
29+
var integral = integrand.ToEntity().Integrate("x");
30+
Assert.DoesNotContain("integral(", integral.Stringize());
31+
Assert.DoesNotContain("NaN", integral.Stringize());
32+
33+
var derivative = integral.Substitute("C", 0).Differentiate("x");
34+
Entity original = integrand.ToEntity();
35+
foreach (var (name, value) in pins)
36+
{
37+
derivative = derivative.Substitute(name, value);
38+
original = original.Substitute(name, value);
39+
}
40+
41+
var compared = 0;
42+
foreach (var at in points)
43+
{
44+
var got = derivative.Substitute("x", at).EvalNumerical();
45+
var want = original.Substitute("x", at).EvalNumerical();
46+
if (got.IsNaN || want.IsNaN)
47+
continue;
48+
compared++;
49+
var difference = Math.Abs((double)(got - want).RealPart)
50+
+ Math.Abs((double)(got - want).ImaginaryPart);
51+
var scale = Math.Max(1.0, Math.Abs((double)want.RealPart));
52+
Assert.True(difference / scale < 1e-8,
53+
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, "
54+
+ $"where the integrand is {want}");
55+
}
56+
Assert.True(compared >= 3,
57+
$"only {compared} of {points.Length} points were comparable for {integrand}");
58+
}
59+
60+
[Theory]
61+
[InlineData("1/(a*c+b*c*x^2+d*sqrt(a+b*x^2))")]
62+
[InlineData("x^2/(a*c+b*c*x^2+d*sqrt(a+b*x^2))")]
63+
[InlineData("1/(x^2*(a*c+b*c*x^2+d*sqrt(a+b*x^2)))")]
64+
[InlineData("1/(c*sqrt(a+b*x^2)+d)")]
65+
public void SymbolicConstants(string integrand)
66+
{
67+
var points = new[] { 0.3, 0.7, 1.1, 1.3 };
68+
DifferentiatesBack(integrand, points, ("a", 2), ("b", 3), ("c", 1.25), ("d", 1));
69+
DifferentiatesBack(integrand, points, ("a", 2), ("b", 3), ("c", 0.5), ("d", 3));
70+
DifferentiatesBack(integrand, points, ("a", 2), ("b", -1), ("c", 1), ("d", 0.5));
71+
DifferentiatesBack(integrand, points, ("a", 2), ("b", 3), ("c", 1), ("d", -0.5));
72+
}
73+
74+
[Fact]
75+
public void ARootBesideXOverTheirDifference()
76+
=> DifferentiatesBack("x*sqrt(2-x^2)/(x-sqrt(2-x^2))", new[] { -0.5, 0.3, 1.2, 1.3 });
77+
}
78+
}

0 commit comments

Comments
 (0)