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 @@ -1572,6 +1572,21 @@ 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.

### Two proportional linears under roots are read as one radical

`sin(a + b (c + d x)^(1/3))/(c e + d e x)^(1/3)` ran past two minutes. The two linears under the
roots are proportional, `c e + d e x = e (c + d x)`, and the rule for roots of two different
linears, which rationalises by `t = ((a x + b)/(c x + d))^(1/q)`, has nothing to rationalise when
their determinant `a d - b c` is zero. A power of the second is now written as the ratio's power
times the same power of the first, the generic reading of a root of a product, and the integrand
is the one-linear question; a ratio known to be negative under an even root is not taken, since
there the two differ by a sign
([#1386](https://github.com/asc-community/AngouriMath/issues/1386)).

| Input | Was (2.5.0) | Now |
|---|---|---|
| `"sin(a+b*(c+d*x)^(1/3))/(c*g+d*g*x)^(1/3)".Integrate("x")` | ran past two minutes | `3 ((c + d x)^(1/3) (-cos(a + b (c + d x)^(1/3)))/b + sin(a + b (c + d x)^(1/3))/b^2)/(d g^(1/3))` |

### 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7042,15 +7042,19 @@ node is Powf(var radical, Number.Rational e) && e is not Number.Integer
return null;
if (!TreeAnalyzer.TryGetPolyLinear(first, x, out var a, out var b) || !TreeAnalyzer.TryGetPolyLinear(second, x, out var c, out var d))
return null;
// Two proportional linears are one radical with a constant in it, and not this
// rule's: their determinant a d - b c is zero, the second linear in t is 0/(a - c t^q)
// and everything it multiplies vanished -- Rubi's
// `sin(a + b (c + d x)^(1/3))/(c e + d e x)^(1/3)` came back as `0 provided ...`.
// Two proportional linears are one radical with a constant in it: their determinant
// a d - b c is zero, and the second linear in t would be 0/(a - c t^q), everything it
// multiplies vanishing -- Rubi's `sin(a + b (c + d x)^(1/3))/(c e + d e x)^(1/3)`
// came back as `0 provided ...`. With `c x + d = k (a x + b)`, a power of the second
// is `k^r` times the same power of the first, the generic reading of a root of a
// product; a known-negative k under an even root is not taken, since there the two
// differ by a sign wherever the first linear is negative. The integrand is then the
// one-linear question, asked again.
// https://github.com/asc-community/AngouriMath/issues/1386
var determinant = (a * d - b * c).InnerSimplified;
if (determinant.Evaled is Number.Complex { IsZero: true }
|| determinant.Vars.Any() && Functions.PartialFractions.Bare(determinant.Simplify()).Evaled is Number.Complex { IsZero: true })
return null;
return AsOneLinearRadical(expr, x, first, second, Functions.PartialFractions.Bare((c / a).Simplify()), integrateByParts);

var t = Variable.CreateUnique(expr, "t_rad");
var w = Variable.CreateUnique(expr, "w_rad");
Expand Down Expand Up @@ -7179,6 +7183,34 @@ static Entity WithWholePowersOfTheSecond(List<(EInteger Power, Entity Coefficien
}
}

/// <summary>
/// <paramref name="expr"/> with every fractional power of <paramref name="second"/>, which is
/// <paramref name="ratio"/> times <paramref name="first"/>, written as the ratio's power
/// times the same power of <paramref name="first"/>, and asked again; null where the ratio
/// is known negative under an even root, or the rewrite leaves the question as it was.
/// </summary>
private static Entity? AsOneLinearRadical(Entity expr, Entity.Variable x, Entity first, Entity second, Entity ratio, bool integrateByParts)
{
if (ratio.ContainsNode(x))
return null;
var negative = ratio.Evaled is Number.Real { IsNegative: true };
var refused = false;
var rewritten = expr.Replace(node =>
{
if (node is not Powf(var @base, Number.Rational exponent) || exponent is Number.Integer || @base != second)
return node;
if (negative && exponent.ERational.Denominator.IsEven)
refused = true;
return MathS.Pow(ratio, exponent) * MathS.Pow(first, exponent);
});
if (refused || rewritten == expr)
return null;
// The same question, respelled: a rule scoped to the question asked is owed it, as in
// SolveByCancellingWithFunctionsAsIndeterminates, and with one linear left under a
// root this rule does not see it again.
return Integration.ComputeAsTheSameQuestion(rewritten.InnerSimplified, x, integrateByParts);
}

/// <summary>
/// Whether <paramref name="expr"/> is a quotient of two polynomials linear in
/// <paramref name="x"/>, the one below with a slope.
Expand Down
29 changes: 23 additions & 6 deletions Sources/Tests/UnitTests/Calculus/TrigonometricPowerProductTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -208,14 +208,31 @@ public void AFractionalPowerOfAProductKeepsItsSign(string integrand)
/// Two proportional linears under roots are one radical with a constant in it: the
/// quotient-of-two-radicals rule took them, its determinant was zero, and
/// <c>sin(a + b (c + d x)^(1/3))/(c e + d e x)^(1/3)</c> came back as
/// <c>0 provided ...</c>. https://github.com/asc-community/AngouriMath/issues/1386
/// <c>0 provided ...</c>. With the second root written as <c>e^(1/3)</c> times the
/// first, it is the one-linear question, and is answered. Rubi's 4.1.12, with <c>g</c> for
/// its <c>e</c>, which here is Euler's number.
/// https://github.com/asc-community/AngouriMath/issues/1386
/// </summary>
[Fact]
public void TwoProportionalRadicalsAreNotAQuotientOfTwo()
[Theory]
[InlineData("sin(a + b*(c + d*x)^(1/3))/(c*g + d*g*x)^(1/3)")]
[InlineData("(c + d*x)^(1/2)/(c*g + d*g*x)^(3/2)")]
[InlineData("x*(2*c + 2*d*x)^(1/3)*(c + d*x)^(2/3)")]
public void TwoProportionalRadicalsAreOne(string integrand)
{
var integral = "sin(a + b*(c + d*x)^(1/3))/(c*e2 + d*e2*x)^(1/3)".ToEntity().Integrate("x");
Assert.DoesNotContain("NaN", integral.Stringize());
Assert.NotEqual("0", (integral.Substitute("C", 0) is Entity.Providedf(var inner, _) ? inner : integral.Substitute("C", 0)).Simplify().Stringize());
var integral = integrand.ToEntity().Integrate("x");
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pinned(Entity e) => e.Substitute("a", "1/3".ToEntity()).Substitute("b", "2/3".ToEntity())
.Substitute("c", "5/4".ToEntity()).Substitute("d", "1/2".ToEntity()).Substitute("g", "3/2".ToEntity());
var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
var original = Pinned(integrand.ToEntity());
foreach (var at in new[] { 0.35, 0.9, 1.45, 2.3 })
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
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-9, $"d/dx of {integral} is {got} at x = {at}, where {integrand} is {want}");
}
}

/// <summary>
Expand Down
Loading