diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
index 03f8400c1..07905206e 100644
--- a/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
@@ -420,16 +420,37 @@ private static bool AlreadyBeingDifferentiated(Entity quotient)
///
/// Whether differentiating both parts has left a quotient bigger than the one it came
- /// from by more than the derivative of a power or a logarithm accounts for. Each step
- /// asks what the two parts of its quotient tend to, and a bigger quotient makes those
- /// two questions harder than the one being answered, so a step that grows is a step
- /// away from an answer rather than towards one. The room left over is measured: the
- /// steps that do reach an answer add two nodes, and the most any of them was seen to
- /// add is six, while one step of x^(3/2) * sqrt(1 + 1/x^2) / x^2 adds eighteen and
- /// leaves the rule tens of seconds of work that ends in nothing.
+ /// from by more than a step on the way to an answer accounts for. Each step asks what
+ /// the two parts of its quotient tend to, and a bigger quotient makes those two
+ /// questions harder than the one being answered, so a step that grows without bound is
+ /// a step away from an answer rather than towards one. One step of
+ /// x^(3/2) * sqrt(1 + 1/x^2) / x^2 adds twelve nodes and the next adds twenty-six,
+ /// which is that shape.
///
+ ///
+ /// The room was a flat eight nodes, which is what growth looks like when the divisor is
+ /// a power of the variable: differentiating that shrinks it, so the only thing that
+ /// grows is the dividend and it grows by addition. A divisor that is a *product* of
+ /// vanishing factors grows by the product rule instead, which is multiplication --
+ /// x^2 * sin(x)^2 differentiates into a sum of two products, each about the size of the
+ /// whole. Such a chain still terminates, since the order at which the divisor vanishes
+ /// falls by one at every step, but it grows for as long as it takes to get there and
+ /// only then collapses: 1/x^2 - 1/sin(x)^2 at 0 goes 17 -> 26 -> 33 -> 46 nodes before
+ /// four steps settle it at -1/3. A flat budget cannot say that, and turned the first of
+ /// those steps away, leaving the descent to answer +oo - +oo -- NaN, the claim that the
+ /// limit does not exist -- for a limit that exists.
+ ///
+ /// So the room is the larger of the eight nodes and three fifths again, which is
+ /// proportional where the growth is proportional and unchanged for the small quotients
+ /// the flat budget was measured on. It is wider than the old rule everywhere, so no
+ /// chain that reached an answer before is turned away now. It costs nothing on the
+ /// shape that motivated the flat budget either: nineteen nodes to thirty-one is above
+ /// three fifths again as surely as it is above eight more, so that chain still stops at
+ /// the same step, and the limit still answers 0 in the same 240 ms. The corpus is
+ /// unchanged at 112/117 and, in total time, within noise of where it was.
+ ///
private static bool GrewTooMuch(Entity quotient, Entity applied)
- => applied.Nodes.Count() > quotient.Nodes.Count() + 8;
+ => applied.Nodes.Count() > Math.Max(quotient.Nodes.Count() + 8, quotient.Nodes.Count() * 8 / 5);
[ThreadStatic] private static bool suppresslHopital;
@@ -438,12 +459,27 @@ private static bool GrewTooMuch(Entity quotient, Entity applied)
/// taken into the latter. A product with no reciprocal factor in it comes back with a
/// denominator of 1, which is what makes this usable term by term below.
///
+ ///
+ /// A power of a quotient is one of these too, and is how a squared cosecant arrives:
+ /// csc(x) is written 1/sin(x) in front of the descent, so csc(x)^2 reaches here as
+ /// (1/sin(x))^2 with its denominator inside the power rather than at the top. Read as
+ /// a factor with no denominator, csc(x)^2 - 1/x^2 at 0 was never put over a common
+ /// denominator and came back unevaluated where it is 1/3.
+ ///
+ /// Only for a whole power, where (a/b)^n is a^n/b^n exactly. At a half it is not: the
+ /// square root of 1/(-1) is i and the quotient of the two square roots is -i, and a
+ /// limit taken of the second is a limit of a different function.
+ ///
private static (Entity Numerator, Entity Denominator) SplitProduct(Entity expr)
{
Entity numerator = 1, denominator = 1;
foreach (var factor in Mulf.LinearChildren(expr))
switch (factor)
{
+ case Powf(Divf(var dividend, var divisor), Integer { IsPositive: true } power):
+ numerator *= dividend.Pow(power);
+ denominator *= divisor.Pow(power);
+ break;
case Powf(var @base, Real { IsNegative: true } power):
denominator *= @base.Pow(-power);
break;
diff --git a/Sources/Tests/UnitTests/Calculus/DifferenceOfFractionsTest.cs b/Sources/Tests/UnitTests/Calculus/DifferenceOfFractionsTest.cs
index 05cee66c1..a26b447c8 100644
--- a/Sources/Tests/UnitTests/Calculus/DifferenceOfFractionsTest.cs
+++ b/Sources/Tests/UnitTests/Calculus/DifferenceOfFractionsTest.cs
@@ -37,6 +37,24 @@ private static void AssertLimit(string expression, string destination, string ex
public void ADifferenceOfDivergentFractionsGoesOverACommonDenominator(string expression, string destination, string expected) =>
AssertLimit(expression, destination, expected);
+ ///
+ /// https://github.com/asc-community/AngouriMath/issues/727.
+ ///
+ /// The same difference with both denominators squared. Putting it over a common
+ /// denominator is not enough on its own here: the denominator that comes out is a
+ /// product of two vanishing factors, so l'Hopital's rule differentiates it by the
+ /// product rule and the quotient grows at every step before it collapses. The rule
+ /// used to refuse the first of those steps and leave the descent to answer
+ /// +oo - +oo, which is NaN -- the claim that the limit does not exist -- where it
+ /// is -1/3.
+ ///
+ [Theory]
+ [InlineData("1/x ^ 2 - 1/sin(x) ^ 2", "0", "-1/3")]
+ [InlineData("1/sin(x) ^ 2 - 1/x ^ 2", "0", "1/3")]
+ [InlineData("csc(x) ^ 2 - 1/x ^ 2", "0", "1/3")]
+ public void ADifferenceOfSquaredFractionsIsSettledToo(string expression, string destination, string expected) =>
+ AssertLimit(expression, destination, expected);
+
///
/// A sum is only put over a common denominator where a denominator contains the variable
/// -- those are the ones that vanish or diverge and make the difference indeterminate.