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 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
bar, and so was the sum with its quadratic written over a constant, `sqrt((a f^2 + e x (2 d + e x))/f^2)`, as Rubi's
1.3.2 writes it. The substitution read one radicand, and a quadratic only as a polynomial. `sqrt(g Q)/sqrt(Q)` is
constant wherever both roots are defined, for either sign of `g`, so it is named while the question is asked and put
back after; and a quadratic over a constant is read through the constant
([#718](https://github.com/asc-community/AngouriMath/issues/718)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"(d + e*x + f*sqrt((a*f^2 + e*x*(2*d + e*x))/f^2))^n".ToEntity().Integrate("x")` | `integral(...)` | 227 characters |
| `"(a*g + 2*d*e*g*x/f^2 + e^2*g*x^2/f^2)^(1/2)*(d + e*x + f*sqrt(a + 2*d*e*x/f^2 + e^2*x^2/f^2))^n".ToEntity().Integrate("x")` | `integral(...)` | 539 characters |
| `"(d + e*x + f*sqrt(a + 2*d*e*x/f^2 + e^2*x^2/f^2))^n/(a*g + 2*d*e*g*x/f^2 + e^2*g*x^2/f^2)^(1/2)".ToEntity().Integrate("x")` | `integral(...)` | 253 characters |

### A whole power of the radicand under Euler's substitution is read as one

**Answers where there were none, and none where they were wrong.** Euler's substitution writes the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18870,6 +18870,76 @@ private static bool VanishesIdentically(Entity expr)
return Functions.PartialFractions.Bare(inner.Simplify()).Evaled is Number.Complex { IsZero: true };
}

/// <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
/// <c>a + 2 d e x/f^2 + e^2 x^2/f^2</c>, and the first was not read as a quadratic.
/// </summary>
private static bool TryReadAQuadraticOverAConstant(Entity radicand, Entity.Variable x,
[System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out Entity? a,
[System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out Entity? b,
[System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out Entity? c)
{
if (TreeAnalyzer.TryGetPolyQuadratic(radicand, x, out a, out b, out c))
return true;
if (radicand is Divf(var over, var constant) && !constant.ContainsNode(x)
&& TreeAnalyzer.TryGetPolyQuadratic(over, x, out var overA, out var overB, out var overC))
{
(a, b, c) = ((overA / constant).InnerSimplified, (overB / constant).InnerSimplified, (overC / constant).InnerSimplified);
return true;
}
return false;
}

/// <summary>
/// <paramref name="expr"/> with every square root of a constant multiple of one quadratic
/// <c>Q</c> written as a named constant times <c>sqrt(Q)</c>, and the names' values; null
/// where there is one radicand, or radicands that are not multiples of one.
/// </summary>
/// <remarks>
/// <c>sqrt(g Q) (d + e x + f sqrt(Q))^n</c>: the ratio <c>sqrt(g Q)/sqrt(Q)</c> is constant
/// wherever both roots are defined, whatever the sign of <c>g</c>, so it comes out of the
/// integral named and goes back in after. The radicand kept is the one a power of a sum
/// holds, the substitution's own. Rubi's 1.3.2.
/// https://github.com/asc-community/AngouriMath/issues/718
/// </remarks>
private static (Entity Rewritten, List<(Entity.Variable Name, Entity Value)> Ratios)? WithTheRootsOfMultiplesAsOne(Entity expr, Entity.Variable x)
{
static bool IsARoot(Entity node, Entity.Variable x)
=> node is Powf(var @base, Number.Rational r) && r is not Number.Integer
&& r.ERational.Denominator.Equals(EInteger.FromInt32(2)) && @base.ContainsNode(x);
var radicands = expr.Nodes.Where(node => IsARoot(node, x)).Select(node => ((Powf)node).Base).Distinct().ToList();
if (radicands.Count < 2)
return null;
var kept = expr.Nodes
.OfType<Powf>()
.Where(power => !power.Exponent.ContainsNode(x) && !IsARoot(power, x) && power.Base.Nodes.Any(inner => IsARoot(inner, x)))
.SelectMany(power => power.Base.Nodes.Where(inner => IsARoot(inner, x)).Select(inner => ((Powf)inner).Base))
.FirstOrDefault() ?? radicands[0];
if (!TryReadAQuadraticOverAConstant(kept, x, out var a, out var b, out var c) || VanishesIdentically(a))
return null;
var ratios = new List<(Entity.Variable, Entity)>();
var byRadicand = new Dictionary<Entity, Entity.Variable>();
foreach (var other in radicands)
{
if (other == kept)
continue;
if (!TryReadAQuadraticOverAConstant(other, x, out var oa, out var ob, out var oc))
return null;
var factor = (oa / a).InnerSimplified;
if (factor.ContainsNode(x) || !VanishesIdentically(ob - factor * b) || !VanishesIdentically(oc - factor * c))
return null;
var name = Variable.CreateUnique(expr + ratios.Aggregate((Entity)Number.Integer.Zero, (sum, pair) => sum + pair.Item1), "k_ratio");
ratios.Add((name, MathS.Sqrt(other) / MathS.Sqrt(kept)));
byRadicand[other] = name;
}
var rewritten = expr.Replace(node =>
node is Powf(var @base, Number.Rational r) && byRadicand.TryGetValue(@base, out var ratio) && IsARoot(node, x)
? MathS.Pow(ratio, Number.Integer.Create(r.ERational.Numerator)) * MathS.Pow(kept, r)
: node);
return (rewritten, ratios);
}

/// <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 Expand Up @@ -18921,6 +18991,15 @@ private static bool VanishesIdentically(Entity expr)
/// </remarks>
internal static Entity? SolveByEulerSubstitution(Entity expr, Entity.Variable x, bool onlyBySumItWrites = false)
{
// Roots of constant multiples of one quadratic are one root: see the method below.
if (WithTheRootsOfMultiplesAsOne(expr, x) is var (asOne, ratios))
{
if (SolveByEulerSubstitution(asOne, x, onlyBySumItWrites) is not { } inTheRatios)
return null;
foreach (var (name, value) in ratios)
inTheRatios = inTheRatios.Substitute(name, value);
return inTheRatios;
}

// One square root of a quadratic in x, and otherwise a rational function of x --
// or, besides those, powers of `x + sqrt(Q)` and `sqrt(Q) - x` to any exponent,
Expand Down Expand Up @@ -18961,7 +19040,7 @@ private static bool VanishesIdentically(Entity expr)
else if (radicand != innerBase) return null;
}
if (radicand is null
|| !TreeAnalyzer.TryGetPolyQuadratic(radicand, x, out var a, out var b, out var c)
|| !TryReadAQuadraticOverAConstant(radicand, x, out var a, out var b, out var c)
|| a.Evaled is Number.Complex { IsZero: true })
return null;
// A missing linear term is the trigonometric substitution's and answered more shortly there.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,25 @@ public sealed class SumOfALinearAndARootAsEulersVariableIntegralTest
// And beside powers of the radicand, whole and not, before the split expands them.
[InlineData("(a + 2*d*k*x/f^2 + k^2*x^2/f^2)^2*(d + k*x + f*sqrt(a + 2*d*k*x/f^2 + k^2*x^2/f^2))^n")]
[InlineData("(a + 2*d*k*x/f^2 + k^2*x^2/f^2)^(3/2)*(d + k*x + f*sqrt(a + 2*d*k*x/f^2 + k^2*x^2/f^2))^n")]
public void IsIntegratedInTheSum(string integrand)
public void IsIntegratedInTheSum(string integrand) => DifferentiatesBack(integrand, 2.5);

// The radicand written over `f^2` as Rubi writes it, and the root of a constant multiple
// of it beside the sum: `sqrt(g Q)` is `sqrt(Q)` times a ratio constant wherever both are
// defined, named while the question is asked, and so for either sign of `g`.
[Theory]
[InlineData("(d + k*x + f*sqrt((a*f^2 + k*x*(2*d + k*x))/f^2))^n", 2.5)]
[InlineData("(d + k*x + f*sqrt((a*f^2 + k*x*(2*d + k*x))/f^2))^n/((a*f^2 + k*x*(2*d + k*x))/f^2)^(1/2)", 2.5)]
[InlineData("(a*g + 2*d*k*g*x/f^2 + k^2*g*x^2/f^2)^(1/2)*(d + k*x + f*sqrt(a + 2*d*k*x/f^2 + k^2*x^2/f^2))^n", 2.5)]
[InlineData("(a*g + 2*d*k*g*x/f^2 + k^2*g*x^2/f^2)^(1/2)*(d + k*x + f*sqrt(a + 2*d*k*x/f^2 + k^2*x^2/f^2))^n", -2.5)]
[InlineData("(d + k*x + f*sqrt((a*f^2 + k*x*(2*d + k*x))/f^2))^n/((a*f^2*g + k*g*x*(2*d + k*x))/f^2)^(1/2)", -2.5)]
public void WrittenOverItsDenominatorOrBesideTheRootOfAMultiple(string integrand, double g) => DifferentiatesBack(integrand, g);

private static void DifferentiatesBack(string integrand, double g)
{
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", 1.3).Substitute("b", 0.6).Substitute("d", 0.7)
.Substitute("f", 1.1).Substitute("k", 0.9).Substitute("n", 0.37);
.Substitute("f", 1.1).Substitute("k", 0.9).Substitute("n", 0.37).Substitute("g", g);
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { -2.5, -1.2, 0.3, 0.8, 1.4 })
Expand Down
Loading