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
18 changes: 18 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,24 @@ Rubi's 1.2.1.4, all 632 problems that count: 250 to 409, no row lost, 61 timeout
new timeouts have a radicand of four symbols beside a power of `x`, and went from a quick decline
to one past the budget.

### A whole power of a perfect square in a power of the variable is written as a power of its root

`1/(a^2 + 2 a b x^2 + b^2 x^4)^2` was left unevaluated. The quartic is `b^2 (x^2 + a/b)^2`, so a
whole power of it is `(b^2)^n (x^2 + a/b)^(2n)`, exactly and with no sign, and the rational
integrator reads a power of `x^2 + a/b` where it declined the quartic. It is written so at the
question asked, for a square written out as a sum with a symbol in it and `x` below the bar. It
is not written so in a polynomial, or where the square is already written as a power. Answers
that came out in the expanded square before now come out in `x^2 + a/b`.

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"1/(a^2+2*a*b*x^2+b^2*x^4)^2".Integrate("x")` | left unevaluated | the antiderivative, in `x^2 + a/b` |
| `"x/(a^2+2*a*b*x^2+b^2*x^4)^3".Integrate("x")` | a piecewise on `b^2 = 0`, in `2 b^2 x^2 + 2 a b` | `-(x^2 + a/b)^(-5)/(10 (b^2)^3)` |
| `"x^2*(a^2+2*a*b*x^3+b^2*x^6)^(-2)".Integrate("x")` | a piecewise on `b^2 = 0`, in `2 b^2 x^3 + 2 a b` | `-(b^2)^(-2) b^3/(9 (a + b x^3)^3)` |

Rubi's 1.2.2.2, 1.2.2.4, 1.2.2.7 and 1.2.3.2, the 574 problems that count with a perfect square
written with symbols: 388 to 409, no row lost.

### `binomial(n, k)` is a function

**Addition, not silent.** The binomial coefficient is a node, `Entity.Binomialf`, spelled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10602,12 +10602,28 @@ private static bool IsAPerfectSquareDiscriminant(Entity a, Entity b, Entity c)
// `u = e^(2x)` cost twenty seconds that way. A bare `x^2` is the exception at any
// depth, nothing else reading `(x^2)^(3/2)`.
var atTheTop = Integration.AnsweringTheQuestionAsked;
// A whole power too, at the top, of a square with a symbol in it, and in an integrand
// with x below the bar: `(a^2 + 2 a b x^2 + b^2 x^4)^(-2)` is `(b^2)^(-2) (x^2 + a/b)^(-4)`,
// exact and with no sign, and the rational integrator reads a power of `x^2 + a/b`
// where it declined the quartic. Not in a polynomial, which is answered as one already;
// and read off the whole integrand, since `1/P^2` holds `P^2` with a positive exponent.
// And only of a square written out as a sum: `((a + b - b u^2)^2)^(-1)`, what the
// hyperbolic tangent makes of `coth(x)^2/(a + b sech(x)^2)^2`, is a square written as
// one, and taken apart into `(u^2 - a/b - 1)^(-2)` it went from under a second to past
// a minute.
bool? xBelowTheBar = null;
bool XIsBelowTheBar()
=> xBelowTheBar ??= Functions.SingleQuotient.Of(Functions.SingleQuotient.Combine(expr)).Denominator.ContainsNode(x);
bool MayBeARootOfASquare(Entity node, Entity.Variable x)
=> node is Powf(var radicand, Number.Rational r) && r.ERational.Denominator.Equals(EInteger.FromInt32(2))
&& (atTheTop || r.ERational.Equals(ERational.Create(1, 2))
|| radicand is Powf(var @base, var degree) && @base == x && degree == Number.Integer.Create(2))
&& radicand.ContainsNode(x) && radicand.Complexity <= (atTheTop ? 40 : 12)
&& radicand.Nodes.Count(inner => inner == x) <= 2;
=> node is Powf(var radicand, Number.Rational r)
&& (r.ERational.Denominator.Equals(EInteger.FromInt32(2))
&& (atTheTop || r.ERational.Equals(ERational.Create(1, 2))
|| radicand is Powf(var @base, var degree) && @base == x && degree == Number.Integer.Create(2))
&& radicand.Complexity <= (atTheTop ? 40 : 12)
|| atTheTop && r is Number.Integer && r != Number.Integer.Zero && r != Number.Integer.One
&& radicand is Sumf or Minusf
&& radicand.Complexity <= 40 && radicand.Vars.Any(symbol => symbol != x) && XIsBelowTheBar())
&& radicand.ContainsNode(x) && radicand.Nodes.Count(inner => inner == x) <= 2;
if (!expr.Nodes.Any(node => MayBeARootOfASquare(node, x)))
return null;
var changed = false;
Expand All @@ -10631,6 +10647,11 @@ bool MayBeARootOfASquare(Entity node, Entity.Variable x)
if (monomials.Keys.Any(k => !k.IsZero && !k.Equals(half) && !k.Equals(degree))
|| !half.Equals(EInteger.One) && !monomials.ContainsKey(EInteger.Zero))
return node;
// A whole power of a square in x itself is a rational function the splits answer
// already, in the form they give; in a power of x, `x^2 + a/b`, they did not.
var whole = r is Number.Integer;
if (whole && half.Equals(EInteger.One))
return node;
var a = monomials[degree];
var b = monomials.TryGetValue(half, out var b1) ? b1 : Number.Integer.Zero;
var c = monomials.TryGetValue(EInteger.Zero, out var c0) ? c0 : Number.Integer.Zero;
Expand All @@ -10640,16 +10661,14 @@ bool MayBeARootOfASquare(Entity node, Entity.Variable x)
// `a^2 + 2 a b x + b^2 x^2` is the square Rubi writes -- whose condition travels
// with the answer. The other two coefficients are real or symbolic, not a
// number off the line, for the same reason.
Entity leading;
if (a.Evaled is Number.Real { IsPositive: true })
leading = a;
else if (IsPositiveForARealParameter(a, x, out var leadingAssumed, assumed))
// A whole power needs no sign of anything: (a (w + h)^2)^n is a^n (w + h)^(2n).
if (!whole && a.Evaled is not Number.Real { IsPositive: true })
{
leading = a;
if (!IsPositiveForARealParameter(a, x, out var leadingAssumed, assumed))
return node;
assumed = leadingAssumed;
}
else
return node;
var leading = a;
if (b.Evaled is Number.Complex and not Number.Real || c.Evaled is Number.Complex and not Number.Real
|| !IsAPerfectSquareDiscriminant(a, b, c))
return node;
Expand All @@ -10660,14 +10679,14 @@ bool MayBeARootOfASquare(Entity node, Entity.Variable x)
if (h.Vars.Any())
h = Functions.PartialFractions.Bare(h.Simplify());
var linear = h == Number.Integer.Zero ? w : (w + h).InnerSimplified;
var twoR = Number.Integer.Create(r.ERational.Numerator);
var twoR = Number.Integer.Create(whole ? r.ERational.Numerator * EInteger.FromInt32(2) : r.ERational.Numerator);
Entity power = twoR == Number.Integer.One ? linear : MathS.Pow(linear, twoR);
// The sign in front of the integral rather than inside it: it is constant
// between the linear factor's zeros, and a rule below that differentiates the
// integrand cannot evaluate `derivative(sgn(...))` -- which is the exception
// `sqrt(a^2 + 2abx + b^2x^2) sqrt(c + ex + dx^2)` threw with it left in place.
// None where it is plainly one: an even power of x plus a positive number.
if (!(half.IsEven && h.Evaled is Number.Real { IsPositive: true }))
if (!whole && !(half.IsEven && h.Evaled is Number.Real { IsPositive: true }))
signs = signs * MathS.Signum(linear);
changed = true;
return leading == Number.Integer.One ? power : MathS.Pow(leading, r) * power;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,40 @@ Entity Pin(Entity e) => e.Substitute("a", -0.9).Substitute("b", 1.7).Substitute(
Assert.True(compared >= 5, $"only {compared} points were comparable for {integrand}");
}

/// <summary>
/// A whole power of the same square: <c>(a^2 + 2 a b x^2 + b^2 x^4)^(-2)</c> is
/// <c>(b^2)^(-2) (x^2 + a/b)^(-4)</c>, exactly and with no sign, a power of a quadratic in
/// <c>x^2</c> the rational integrator reads. Pinned with <c>a/b</c> positive, so that the
/// square has no real zero among the points.
/// </summary>
[Theory]
[InlineData("1/(a^2 + 2*a*b*x^2 + b^2*x^4)^2")]
[InlineData("x/(a^2 + 2*a*b*x^2 + b^2*x^4)^3")]
[InlineData("x^2*(a^2 + 2*a*b*x^3 + b^2*x^6)^(-2)")]
public void AWholePowerOfASquareInAPowerOfTheVariable(string integrand)
{
var integral = integrand.ToEntity().Integrate("x").Substitute("C", 0);
Assert.DoesNotContain("integral(", integral.Stringize());
Assert.DoesNotContain("NaN", integral.Stringize());
Entity Pin(Entity e) => e.Substitute("a", 0.9).Substitute("b", 1.7);
var derivative = Pin(integral).Differentiate("x");
var original = Pin(integrand.ToEntity());
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) + Math.Abs((double)want.ImaginaryPart));
Assert.True(difference / scale < 1e-9,
$"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
Assert.True(compared >= 5, $"only {compared} points were comparable for {integrand}");
}

[Fact]
public void TheSignIsTheSignOfTheLinearFactor()
{
Expand Down
Loading