Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,25 @@ secant too now, and the integrand is `sgn(cos(x)) (1 + u^2)^(3/2)/(a + b u)^2` i
| `"sec(x)^7/(a + b*tan(x))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,253 characters |
| `"sec(x)/(a + b*cot(x))".ToEntity().Integrate("x")` | `integral(...)` | 571 characters |

### A sum with the root of a quadratic below the bar is cleared by its conjugate

**Answers where there were none.** `1/(a c + b c x^2 + d sqrt(a + b x^2))` was declined: Euler's substitution
writes `sqrt(b)` into every coefficient for a symbolic leading coefficient, and the rational integrator could not
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
one beside the root, and each is answered
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

The symbolic answers are large because each piece's answer is piecewise in the signs of the constants, and adding
two of them multiplies their cases.

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/(a*c + b*c*x^2 + d*sqrt(a + b*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 3,464 characters |
| `"x^2/(a*c + b*c*x^2 + d*sqrt(a + b*x^2))".ToEntity().Integrate("x")` | `integral(...)` | 1,605 characters |
| `"1/(x^2*(a*c + b*c*x^2 + d*sqrt(a + b*x^2)))".ToEntity().Integrate("x")` | `integral(...)` | 5,052 characters |
| `"1/(c*sqrt(a + b*x^2) + d)".ToEntity().Integrate("x")` | `integral(...)` | 2,888 characters |
| `"x*sqrt(2 - x^2)/(x - sqrt(2 - x^2))".ToEntity().Integrate("x")` | `integral(...)` | 195 characters |

### A quadratic with rational roots takes Euler's third substitution at one of them

**Answers where there were none.** `1/(x + sqrt(3 - 2x - x^2))` was declined. Its leading coefficient is negative and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18955,6 +18955,82 @@ node is Powf(var @base, Number.Rational r) && byRadicand.TryGetValue(@base, out
return (rewritten, ratios);
}

/// <summary>
/// A rational function of <c>x</c> and one square root of a quadratic <c>Q</c>, with the
/// root in a sum below the bar, written over a denominator free of it:
/// <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>,
/// a rational function and one beside the root, each asked of the chain.
/// </summary>
/// <remarks>
/// Rubi's <c>1/(a c + b c x^2 + d sqrt(a + b x^2))</c>: Euler's substitution takes the
/// generic first substitution for a symbolic leading coefficient, with <c>sqrt(b)</c> in
/// every coefficient, and was declined. Over the conjugate it is
/// <c>(c Q - d sqrt(Q))/(c^2 Q^2 - d^2 Q)</c>, and both pieces are answered. It runs after
/// every rule that answers the numeric spellings. Only a sum: a root alone below the bar,
/// <c>C = 0</c>, is the root moved above it, which the chain writes back, and <c>D = 0</c>
/// has nothing to clear.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
internal static Entity? SolveByTheConjugateOfASumWithARootOfAQuadraticBelowTheBar(Entity expr, Entity.Variable x, bool integrateByParts)
{
Entity? radicand = null;
foreach (var node in expr.Nodes)
if (node is Powf(var @base, Number.Rational r) && r is not Number.Integer && @base.ContainsNode(x))
{
if (!r.ERational.Denominator.Equals(EInteger.FromInt32(2)) || !r.ERational.Numerator.CanFitInInt32()
|| radicand is not null && radicand != @base)
return null;
radicand = @base;
}
if (radicand is null || !TreeAnalyzer.TryGetPolyQuadratic(radicand, x, out var a, out _, out _) || VanishesIdentically(a))
return null;
var w = Variable.CreateUnique(expr, "w_root");
var inW = expr.Replace(node => node is Powf(var @base, Number.Rational r) && r is not Number.Integer && @base == radicand
? MathS.Pow(w, Number.Integer.Create(r.ERational.Numerator))
: node);
var (top, bottom) = Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(inW));
if (!bottom.ContainsNode(w) || InTheRoot(top) is not var (above, aboveBeside) || InTheRoot(bottom) is not var (below, belowBeside)
|| VanishesIdentically(below) || VanishesIdentically(belowBeside))
return null;
var over = (below * below - belowBeside * belowBeside * radicand).InnerSimplified;
if (VanishesIdentically(over))
return null;
var rational = (above * below - aboveBeside * belowBeside * radicand).InnerSimplified;
var beside = (aboveBeside * below - above * belowBeside).InnerSimplified;
Entity answer = Number.Integer.Zero;
if (!VanishesIdentically(rational))
{
if (Integration.ComputeIndefiniteIntegral(rational / over, x, integrateByParts) is not { } first)
return null;
answer += first;
}
if (!VanishesIdentically(beside))
{
if (Integration.ComputeIndefiniteIntegral(beside * MathS.Sqrt(radicand) / over, x, integrateByParts) is not { } second)
return null;
answer += second;
}
return answer;

// A polynomial in w as A + B w, with w^2 = Q.
(Entity Whole, Entity Beside)? InTheRoot(Entity side)
{
if (!TreeAnalyzer.TryGetPolynomial(side.Expand().InnerSimplified, w, out var monomials) || monomials.Keys.Any(k => k.Sign < 0))
return null;
Entity whole = Number.Integer.Zero;
Entity besideTheRoot = Number.Integer.Zero;
foreach (var pair in monomials)
{
// Q^0 written as a power is `1 provided not Q = 0`, a condition the pieces would carry.
var k = pair.Key.ToInt32Checked();
var term = k / 2 == 0 ? pair.Value : pair.Value * MathS.Pow(radicand, k / 2);
if (k % 2 == 0) whole += term;
else besideTheRoot += term;
}
return (whole.InnerSimplified, besideTheRoot.InnerSimplified);
}
}

/// <summary>
/// A rational function of <c>x</c> and one square root of a quadratic in <c>x</c>,
/// rationalised by an Euler substitution and handed to the rational integrator.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,9 @@ private static Entity Normalized(Entity expr, Entity.Variable x) =>
// `A - B y` whose A/B agrees with a branch of y at the poles, found by an ansatz
// over Padé approximants.
if ((answer = SquareRootLogarithmAnsatz.Integrate(expr, x)) is { }) return answer;
// And a sum with the root below the bar, over its conjugate: after every rule above,
// which answer the numeric spellings, `1/(a c + b c x^2 + d sqrt(a + b x^2))` among them.
if ((answer = IndefiniteIntegralSolver.SolveByTheConjugateOfASumWithARootOfAQuadraticBelowTheBar(expr, x, integrateByParts)) is { }) return answer;
// Product-to-sum among the rewrites rather than before them, because a product of
// trigonometric functions of *equal* arguments is a power and wants a different tool;
// this only fires where the arguments differ, which is exactly what every substitution
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//
// 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
{
/// <summary>
/// A sum with the square root of a quadratic below the bar, cleared by its conjugate.
/// <a href="https://github.com/asc-community/AngouriMath/issues/718">#718</a>
/// </summary>
/// <remarks>
/// 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>
/// neighbours had no antiderivative: Euler's substitution declines a symbolic leading
/// coefficient. Each answer is differentiated back with the constants pinned on both signs of
/// <c>b</c> and of <c>a c^2 - d^2</c>, at points where the root is real.
/// </remarks>
[Trait("Area", "Calculus")]
public sealed class TheConjugateOfASumWithARootBelowTheBarIntegralTest
{
private static void DifferentiatesBack(string integrand, double[] points, params (string, double)[] pins)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());

var derivative = integral.Substitute("C", 0).Differentiate("x");
Entity original = integrand.ToEntity();
foreach (var (name, value) in pins)
{
derivative = derivative.Substitute(name, value);
original = original.Substitute(name, value);
}

var compared = 0;
foreach (var at in points)
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
if (got.IsNaN || 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));
Assert.True(difference / scale < 1e-8,
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, "
+ $"where the integrand is {want}");
}
Assert.True(compared >= 3,
$"only {compared} of {points.Length} points were comparable for {integrand}");
}

[Theory]
[InlineData("1/(a*c+b*c*x^2+d*sqrt(a+b*x^2))")]
[InlineData("x^2/(a*c+b*c*x^2+d*sqrt(a+b*x^2))")]
[InlineData("1/(x^2*(a*c+b*c*x^2+d*sqrt(a+b*x^2)))")]
[InlineData("1/(c*sqrt(a+b*x^2)+d)")]
public void SymbolicConstants(string integrand)
{
var points = new[] { 0.3, 0.7, 1.1, 1.3 };
DifferentiatesBack(integrand, points, ("a", 2), ("b", 3), ("c", 1.25), ("d", 1));
DifferentiatesBack(integrand, points, ("a", 2), ("b", 3), ("c", 0.5), ("d", 3));
DifferentiatesBack(integrand, points, ("a", 2), ("b", -1), ("c", 1), ("d", 0.5));
DifferentiatesBack(integrand, points, ("a", 2), ("b", 3), ("c", 1), ("d", -0.5));
}

[Fact]
public void ARootBesideXOverTheirDifference()
=> DifferentiatesBack("x*sqrt(2-x^2)/(x-sqrt(2-x^2))", new[] { -0.5, 0.3, 1.2, 1.3 });
}
}
Loading