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.