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
Original file line number Diff line number Diff line change
Expand Up @@ -15262,6 +15262,61 @@ private static bool TryWriteAPairOfLinearsOverARadicand(Entity expr, Entity.Vari
};
}

/// <summary>
/// Whether <paramref name="expr"/> over <paramref name="derivative"/> takes two values at
/// two points where <paramref name="candidate"/>, a sine, cosine, tangent, cotangent,
/// secant or cosecant of a linear, takes one -- every other symbol pinned -- which no
/// function of the candidate does. False for any other candidate, and wherever a value
/// cannot be had or the candidate itself does not repeat there.
/// </summary>
private static bool TheQuotientDisagreesWhereTheCandidateRepeats(Entity expr, Entity derivative, Entity candidate, Entity.Variable x)
{
Entity? argument = candidate switch
{
Sinf(var a) => a, Cosf(var a) => a, Tanf(var a) => a,
Cotanf(var a) => a, Secantf(var a) => a, Cosecantf(var a) => a,
_ => null,
};
if (argument is null || !TreeAnalyzer.TryGetPolyLinear(argument, x, out var slope, out var intercept)
|| slope.Evaled is Number.Complex { IsZero: true })
return false;
Entity first = Number.Real.Create(EDecimal.FromString("0.37"));
Entity second = candidate switch
{
Sinf or Cosecantf => (MathS.pi - 2 * intercept) / slope - first,
Cosf or Secantf => -2 * intercept / slope - first,
_ => first + MathS.pi / slope,
};
var pinned = new Dictionary<Variable, Entity>();
var values = new[] { "1.37", "0.61", "2.23", "1.91", "0.83", "1.13" };
var index = 0;
foreach (var symbol in expr.Vars.Concat(derivative.Vars).Concat(candidate.Vars).Distinct())
if (symbol != x)
pinned[symbol] = Number.Real.Create(EDecimal.FromString(values[index++ % values.Length]));
Number.Complex? At(Entity function, Entity point)
{
var value = function.Substitute(x, point);
foreach (var pair in pinned)
value = value.Substitute(pair.Key, pair.Value);
try
{
return value.EvalNumerical() is Number.Complex { IsNaN: false } number && number.IsFinite ? number : null;
}
catch (Core.Exceptions.CannotEvalException) { return null; }
}
static bool Close(Number.Complex left, Number.Complex right, double tolerance)
{
var scale = System.Math.Max(1.0, ((Number.Real)left.Abs()).EDecimal.ToDouble());
return ((Number.Real)(left - right).Abs()).EDecimal.ToDouble() <= tolerance * scale;
}
if (At(candidate, first) is not { } candidateFirst || At(candidate, second) is not { } candidateSecond
|| !Close(candidateFirst, candidateSecond, 1e-9))
return false;
var quotient = expr / derivative;
return At(quotient, first) is { } quotientFirst && At(quotient, second) is { } quotientSecond
&& !Close(quotientFirst, quotientSecond, 1e-6);
}

/// <summary>
/// Whether <paramref name="expr"/>/<paramref name="reference"/> takes the same value at two
/// points, every other symbol pinned to a fixed value -- a necessary condition for a
Expand Down Expand Up @@ -16415,6 +16470,16 @@ private static bool IsARationalFunctionOfExponentials(Entity expr, Entity.Variab
if (!u.ContainsNode(x) || duDx.Evaled == 0)
continue;

// A candidate that takes one value at two points, where the quotient by its
// derivative takes two, is no substitution, whatever the simplifier makes of it:
// a function of u agrees wherever u does. A sine repeats at pi - theta, a cosine at
// -theta, a tangent a period on; two pinned evaluations settle it where writing the
// quotient in u and simplifying it is milliseconds per candidate, and the
// complement pass below is the same function spelled otherwise. The problems of
// Rubi's 4.7.2, 4.3.10 and 4.4.10 the integrator answers took a fifth less time.
if (!complementEvenPowers && TheQuotientDisagreesWhereTheCandidateRepeats(expr, duDx, u, x))
continue;

// Try to express expr as h(u) * du/dx
// If successful, integral becomes ∫h(u)du
var uSub = Variable.CreateUnique(expr, "u_sub");
Expand Down
29 changes: 29 additions & 0 deletions Sources/Tests/UnitTests/Calculus/IntegrationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,35 @@ public void TestAdvancedTrigSubstitution(string initial, string expected)
Assert.Equal(MathS.Boolean.True, result.EqualTo(expectedResult).Simplify());
}

/// <summary>
/// Substitutions under a sine, cosine, tangent or secant of a linear that the search's
/// repeat test keeps: each integrand over the candidate's derivative is a function of the
/// candidate, so it agrees where the candidate repeats -- at pi - theta for a sine, at
/// -theta for a cosine, a period on for a tangent -- and is written in it.
/// </summary>
[Theory]
[InlineData("sin(3*x + 1)^4 * cos(3*x + 1)")]
[InlineData("cos(2*x)^5 * sin(2*x)")]
[InlineData("tan(2*x)^3 * sec(2*x)^2")]
[InlineData("cos(x)/(1 + sin(x)^2)")]
[InlineData("sec(a*x + b)^2 * tan(a*x + b)^5")]
[InlineData("sin(x) * cos(x)^3/(1 + cos(x)^4)")]
public void ASubstitutionTheRepeatTestKeeps(string integrand)
{
var integral = integrand.Integrate("x").Substitute("C", 0);
Assert.DoesNotContain("integral(", integral.Stringize());
Entity Pin(Entity e) => e.Substitute("a", 0.7).Substitute("b", 0.3);
var derivative = Pin(integral).Differentiate("x");
var original = Pin(integrand.ToEntity());
foreach (var at in new[] { 0.2, 0.5, 0.9, 1.3 })
{
var got = derivative.Substitute("x", at).EvalNumerical();
var want = original.Substitute("x", at).EvalNumerical();
var difference = (double)((Entity.Number.Real)(got - want).Abs()).EDecimal.ToDouble();
Assert.True(difference < 1e-9, $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
}
}

[Theory]
[InlineData("abs(x)", "signum(x) * x^2 / 2 + C")]
[InlineData("abs(x + 0)", "signum(x) * x^2 / 2 + C")]
Expand Down
Loading