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
15 changes: 15 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,21 @@ 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 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
its constant term not a square, so the second substitution wrote `sqrt(3)` into every coefficient and the rational
integrator could not finish. `3 - 2x - x^2` is `(1 - x)(3 + x)`: shifted to a root, the constant term is zero, and the
third substitution answers with rational coefficients throughout. A quadratic with a positive leading coefficient
keeps the first substitution, which was already answering it
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/(x + sqrt(3 - 2*x - x^2))".ToEntity().Integrate("x")` | `integral(...)` | 389 characters |
| `"1/(x + sqrt(3 - 2*x - x^2))^3".ToEntity().Integrate("x")` | `integral(...)` | 1,160 characters |
| `"1/(2*x + sqrt(3 - 2*x - x^2))".ToEntity().Integrate("x")` | `integral(...)` | 253 characters |

### A root of a multiple of the radicand beside Euler's sum is the same root

**Answers where there were none.** `sqrt(g Q) (d + e x + f sqrt(Q))^n` was declined, with the root of `g Q` below the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18870,6 +18870,21 @@ private static bool VanishesIdentically(Entity expr)
return Functions.PartialFractions.Bare(inner.Simplify()).Evaled is Number.Complex { IsZero: true };
}

/// <summary>
/// A rational root of <c>a x^2 + b x + c</c> with rational coefficients, the larger, or
/// null where its roots are not rational.
/// </summary>
private static Entity? RationalRootOf(Entity a, Entity b, Entity c)
{
if (a.Evaled is not Number.Rational ra || b.Evaled is not Number.Rational rb || c.Evaled is not Number.Rational rc
|| ra.Evaled is Number.Complex { IsZero: true })
return null;
var discriminant = (rb * rb - 4 * ra * rc).InnerSimplified;
if (discriminant.Evaled is not Number.Rational { IsNegative: false } || MathS.Sqrt(discriminant).InnerSimplified.Evaled is not Number.Rational root)
return null;
return ((-rb + root) / (2 * ra)).InnerSimplified;
}

/// <summary>
/// <paramref name="radicand"/> as <c>a x^2 + b x + c</c>, read through a constant below the
/// bar as well: Rubi writes <c>(a f^2 + e x (2 d + e x))/f^2</c> for
Expand Down Expand Up @@ -19079,6 +19094,21 @@ node is Powf(var @base, Number.Rational r) && byRadicand.TryGetValue(@base, out
else if (aRational) which = 1;
else if (cRational) which = 2;
else if (aPositive) which = 1;
else if (RationalRootOf(a, b, c) is { } rationalRoot)
{
// Neither root rational and a < 0, the quadratic's own roots are: `3 - 2x - x^2`
// is `(1 - x)(3 + x)`, and the second substitution wrote `sqrt(3)` into every
// coefficient, after which `1/(x + sqrt(3 - 2x - x^2))` was declined. Shifted to a
// root, x = y + r, the constant term is zero and the third substitution's, with
// rational coefficients throughout. Where a > 0 the first substitution answers
// first and faster: `sqrt(12x^2 + 17x + 6)/((2 + 3x)^2 (30 + 31x - 12x^2)^2)`
// took 1.4 s by it and timed out by the shift.
var y = Variable.CreateUnique(expr, "y_euler");
if (SolveByEulerSubstitution(expr.Substitute(x, y + rationalRoot), y) is not { } inY)
return null;
var writtenBack = radicand.Substitute(x, y + rationalRoot).Substitute(y, x - rationalRoot);
return inY.Substitute(y, x - rationalRoot).Replace(node => node == writtenBack ? radicand : node).InnerSimplified;
}
else if (cPositive) which = 2;
else if (aValue is null) which = 1;
else if (cValue is null) which = 2;
Expand Down
12 changes: 12 additions & 0 deletions Sources/Tests/UnitTests/Calculus/EulerSubstitutionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ private static void DifferentiatesBack(string integrand, double[] points, params
[InlineData("sqrt(x^2 + 2*x)/x", new[] { 0.3, 0.9, 1.7, 2.6 })]
public void TheRootAtZero(string integrand, double[] points) => DifferentiatesBack(integrand, points, ("r", 1.7));

/// <summary>
/// Neither <c>sqrt(a)</c> nor <c>sqrt(c)</c> rational, but the quadratic's roots: shifted
/// to one, the root is at zero and the third substitution has rational coefficients,
/// where the second wrote <c>sqrt(3)</c> into every one and was declined. Rubi's 1.3.2.
/// </summary>
[Theory]
[InlineData("1/(x + sqrt(3 - 2*x - x^2))", new[] { -2.5, -1.2, 0.3, 0.8 })]
[InlineData("1/(x + sqrt(3 - 2*x - x^2))^3", new[] { -2.5, -1.2, 0.3, 0.8 })]
[InlineData("x/(x + sqrt(3 - 2*x - x^2))", new[] { -2.5, -1.2, 0.3, 0.8 })]
[InlineData("1/(2*x + sqrt(3 - 2*x - x^2))", new[] { -2.5, -1.2, 0.3, 0.8 })]
public void ARationalRootOfTheQuadratic(string integrand, double[] points) => DifferentiatesBack(integrand, points);

/// <summary>
/// Reached one level down, which is why the rule is not scoped: a nested radical under
/// <c>u = sqrt(1 + x)</c>, and the remainders of by parts against an inverse function.
Expand Down
Loading