Skip to content

Commit 9d2b01a

Browse files
Answer the indeterminate forms that are not quotients (#710)
Of the three indeterminate powers only 1^oo had a rule, the second remarkable limit. 0^0 and oo^0 reached the descent, which substitutes each part's own limit and hands back 0^0, that is NaN -- the claim that the limit does not exist. And a product of something vanishing with something diverging had no reading either, unless one of its factors happened to be written as a reciprocal. Both go over to e^(g * ln f) and to the diverging factor over the reciprocal of the vanishing one, which is the textbook move in each case. The exponent is asked as a limit of its own rather than rewritten in place. A rewrite would only hand the descent a product it reads no better than the power, since the descent substitutes the parts' limits and does not apply l'Hopital's rule to a part; asking outright is what puts the whole machinery behind it. Two guards, both from measurement rather than caution: The reciprocal split keeps precedence where it gives the rule a quotient it can use, since x * e^(-x) comes out as the clean x / e^x and inverting the other half instead does not terminate. But it can also hide the indeterminacy it was meant to expose -- tan(x) * ln(x) splits into sin(x) * ln(x) / cos(x), whose divisor tends to 1 -- so it is used only when what it produces is 0/0 or oo/oo. A base the library cannot differentiate is declined. Every route out of ln(f) runs through differentiating f, and a factorial's derivative wants the digamma function and comes back NaN, so lim x->+oo ((x!) / x^x)^(1/x) had no answer either way and took twenty seconds not to find one. x^x at 0 keeps having no two-sided limit, which is right rather than a gap: x^x is not real to the left of 0, so the 1 that comes back from that side is the complex continuation and not a limit to agree with. The suite pinned that already and it still holds. 19 of 20 measured cases, against 9 before. tan(x) * ln(x) is the one left: it arrives restructured as a quotient rather than a product, so the product reading never sees it. It is NaN on master too. 25 new tests; suite 4597 passed, 0 failed; corpus 111/117 with 0 wrong, 0 error and 0 timeout.
1 parent 70fcbea commit 9d2b01a

3 files changed

Lines changed: 241 additions & 3 deletions

File tree

‎Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ private static Entity ExpandLogarithm(Entity expr)
8888
// it is what answers (1 - cos(x)) / x^2 at 0+ with 1/2 rather than NaN, and
8989
// csc(x) * x with 1: the csc rewrite leaves a product, which the descent does
9090
// not take apart but which the rule reads back as the quotient x / sin(x).
91+
if (SolveAsIndeterminatePower(expr, x, dest, side) is { } byExponent)
92+
return byExponent;
9193
if (ApplylHopitalRule(expr, x, dest, side) is { } lhopital && lhopital.Evaled != MathS.NaN)
9294
return lhopital;
9395
// The one-sided path is the only one with nothing behind it, and the descent
@@ -127,6 +129,8 @@ private static Entity ExpandLogarithm(Entity expr)
127129
// were left with nothing to catch them. The rule is only allowed to improve
128130
// on what is already there: sqrt(x) / sqrt(x + 1) merely turns into its own
129131
// reciprocal, and a NaN from it would claim the limit does not exist.
132+
if (SolveAsIndeterminatePower(expr, x, dest, ApproachFrom.Left) is { } byExponent)
133+
return byExponent;
130134
if (ApplylHopitalRule(expr, x, dest) is { } lhopital && lhopital.Evaled != MathS.NaN)
131135
return lhopital;
132136
// The rewrites are worth their cost only where there is no answer without

‎Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs‎

Lines changed: 122 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,65 @@ private static Entity ApplySecondRemarkable(Entity expr, Variable x, Entity dest
5252
_ => expr
5353
};
5454

55+
/// <summary>
56+
/// The limit of f(x)^g(x) where the pair is indeterminate and the second remarkable
57+
/// limit does not already cover it -- that is, 0^0 and oo^0 -- or <see langword="null"/>
58+
/// where that is not the shape or the exponent settles nothing.
59+
/// </summary>
60+
/// <remarks>
61+
/// The descent substitutes each part's own limit, so both of these arrive as 0^0, which
62+
/// is NaN. Written over as e^(g * ln f), the same question is the limit of a product of
63+
/// something vanishing with something diverging, which the rules below can take apart.
64+
/// <para/>
65+
/// The exponent is asked as a limit of its own rather than rewritten in place, because a
66+
/// rewrite would only hand the descent a product it reads no better than the power: the
67+
/// descent substitutes the parts' limits and does not apply l'Hopital's rule to a part.
68+
/// Asking outright is what puts the whole machinery behind the exponent.
69+
/// <para/>
70+
/// 1^oo is left to <see cref="ApplySecondRemarkable"/>, which answers it more directly,
71+
/// and 0^oo and oo^oo are not indeterminate at all.
72+
/// </remarks>
73+
private static Entity? SolveAsIndeterminatePower(Entity expr, Variable x, Entity dest, ApproachFrom side)
74+
{
75+
if (expr is not Powf(var @base, var power)
76+
|| !@base.ContainsNode(x) || !power.ContainsNode(x)
77+
|| indeterminatePowerDepth >= MaxIndeterminatePowerDepth)
78+
return null;
79+
if (EvalAssumingContinuous(power.Limit(x, dest, side)) != 0)
80+
return null;
81+
var baseLimit = EvalAssumingContinuous(@base.Limit(x, dest, side));
82+
if (baseLimit != 0 && !IsInfiniteNode(baseLimit))
83+
return null;
84+
// Every route out of ln(f) runs through differentiating f, so a base this library
85+
// cannot differentiate is one the rewrite cannot finish on: it would only hand the
86+
// rules an expression with a hole in it and let them work at it. A factorial is the
87+
// case that matters -- its derivative wants the digamma function, which is not here,
88+
// and comes back as NaN -- and lim x->+oo ((x!) / x^x)^(1/x) is the expression. It
89+
// has no answer either way, and without this it takes a long time not to find one.
90+
var derivative = @base.Differentiate(x).InnerSimplified;
91+
if (derivative.Nodes.Any(node => node is Derivativef || node == MathS.NaN))
92+
return null;
93+
94+
indeterminatePowerDepth++;
95+
try
96+
{
97+
if (ComputeLimit((power * MathS.Ln(@base)).InnerSimplified, x, dest, side) is not { } exponent
98+
|| exponent.Evaled == MathS.NaN)
99+
return null;
100+
return MathS.e.Pow(exponent).InnerSimplified;
101+
}
102+
finally { indeterminatePowerDepth--; }
103+
}
104+
105+
/// <summary>
106+
/// How deep the rewriting of one power into another may go. The exponent it asks about
107+
/// is a limit in its own right and may hold a power of the same shape, so without a
108+
/// bound the work would multiply.
109+
/// </summary>
110+
private const int MaxIndeterminatePowerDepth = 3;
111+
112+
[ThreadStatic] private static int indeterminatePowerDepth;
113+
55114
private static bool IsInfiniteNode(Entity expr)
56115
=> expr.ContainsNode("+oo") || expr.ContainsNode("-oo"); // TODO: is it correct?
57116

@@ -248,12 +307,22 @@ private static (Entity Numerator, Entity Denominator) SplitProduct(Entity expr)
248307
/// <c>(sin(x) - x) / (x * sin(x))</c>, which is 0/0 and which the rule settles at 0 in
249308
/// three steps.
250309
/// </summary>
251-
private static Entity? AsQuotient(Entity expr, Variable x)
310+
private static Entity? AsQuotient(Entity expr, Variable x, Entity dest, ApproachFrom side)
252311
{
253312
if (expr is Mulf)
254313
{
314+
// Taking the reciprocal factors out is the better reading where it gives the
315+
// rule something it can use -- x * e^(-x) comes out as the clean x / e^x -- so
316+
// it is tried first. But it can also hide the indeterminacy rather than expose
317+
// it: tan(x) * ln(x) has been written sin(x) / cos(x) * ln(x) by the time it
318+
// arrives, and splitting on the reciprocal gives sin(x) * ln(x) / cos(x), whose
319+
// divisor tends to 1. That is no longer a quotient the rule reads, so the other
320+
// arrangement is tried in its place rather than after it.
255321
var (numerator, denominator) = SplitProduct(expr);
256-
return denominator == 1 ? null : numerator / denominator;
322+
if (denominator != 1 && IsIndeterminateQuotient(numerator, denominator, x, dest, side))
323+
return numerator / denominator;
324+
return AsQuotientOfVanishingAndDiverging(expr, x, dest, side)
325+
?? (denominator == 1 ? null : numerator / denominator);
257326
}
258327
if (expr is not (Sumf or Minusf))
259328
return null;
@@ -277,6 +346,56 @@ private static (Entity Numerator, Entity Denominator) SplitProduct(Entity expr)
277346
return worthIt && combined is { } && common is { } ? (combined / common).InnerSimplified : null;
278347
}
279348

349+
/// <summary>
350+
/// Whether a quotient is one of the two forms l'Hopital's rule reads: 0/0 or oo/oo.
351+
/// </summary>
352+
private static bool IsIndeterminateQuotient(Entity numerator, Entity denominator, Variable x, Entity dest, ApproachFrom side)
353+
{
354+
var above = EvalAssumingContinuous(numerator.Limit(x, dest, side));
355+
var below = EvalAssumingContinuous(denominator.Limit(x, dest, side));
356+
return above == 0 && below == 0 || IsInfiniteNode(above) && IsInfiniteNode(below);
357+
}
358+
359+
/// <summary>
360+
/// A product of something vanishing with something diverging, written as the quotient
361+
/// of the diverging factor by the reciprocal of the vanishing one, or
362+
/// <see langword="null"/> where it is not that shape.
363+
/// </summary>
364+
/// <remarks>
365+
/// This is the other way a product can be indeterminate without being written as a
366+
/// quotient, and the split above cannot see it: <c>sin(x) * ln(x)</c> has no reciprocal
367+
/// factor at all, so both halves go into the numerator and it comes back unchanged.
368+
/// <para/>
369+
/// The diverging factor goes on top and the vanishing one is inverted underneath, not
370+
/// the other way round, though both give an indeterminate quotient. Differentiating
371+
/// <c>ln(x) / csc(x)</c> gets rid of the logarithm and arrives at an answer; the other
372+
/// arrangement, <c>sin(x) / (1 / ln(x))</c>, differentiates into a product of the same
373+
/// shape as the one it started from and goes round.
374+
/// </remarks>
375+
private static Entity? AsQuotientOfVanishingAndDiverging(Entity expr, Variable x, Entity dest, ApproachFrom side)
376+
{
377+
Entity? vanishing = null, diverging = null;
378+
Entity rest = 1;
379+
foreach (var factor in Mulf.LinearChildren(expr))
380+
{
381+
if (!factor.ContainsNode(x))
382+
{
383+
rest *= factor;
384+
continue;
385+
}
386+
var limit = EvalAssumingContinuous(factor.Limit(x, dest, side));
387+
if (vanishing is null && limit == 0)
388+
vanishing = factor;
389+
else if (diverging is null && IsInfiniteNode(limit))
390+
diverging = factor;
391+
else
392+
rest *= factor;
393+
}
394+
return vanishing is { } zero && diverging is { } infinity
395+
? rest * infinity / (1 / zero).InnerSimplified
396+
: null;
397+
}
398+
280399
/// <remarks>
281400
/// The side is carried through because the rule is stated one-sidedly to begin with --
282401
/// the two-sided case is the two one-sided ones agreeing -- so it is the same rule
@@ -304,7 +423,7 @@ private static (Entity Numerator, Entity Denominator) SplitProduct(Entity expr)
304423

305424
private static Entity? ApplylHopitalRuleImpl(Entity expr, Variable x, Entity dest, ApproachFrom side)
306425
{
307-
if (expr is not Divf && AsQuotient(expr, x) is { } quotient)
426+
if (expr is not Divf && AsQuotient(expr, x, dest, side) is { } quotient)
308427
expr = quotient;
309428
if (expr is Divf(var num, var den))
310429
if (EvalAssumingContinuous(num.Limit(x, dest, side)) is var numLimit && EvalAssumingContinuous(den.Limit(x, dest, side)) is var denLimit)
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
//
2+
// Copyright (c) 2019-2022 Angouri.
3+
// AngouriMath is licensed under MIT.
4+
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
5+
// Website: https://am.angouri.org.
6+
//
7+
8+
using AngouriMath;
9+
using AngouriMath.Core;
10+
using AngouriMath.Extensions;
11+
using Xunit;
12+
13+
namespace AngouriMath.Tests.Calculus
14+
{
15+
/// <summary>
16+
/// The indeterminate forms that are not quotients. Of the three powers, only 1^oo had a
17+
/// rule -- the second remarkable limit -- so 0^0 and oo^0 arrived at the descent, which
18+
/// substitutes each part's own limit and hands back 0^0, that is NaN. And a product of
19+
/// something vanishing with something diverging had no reading either unless one of the
20+
/// factors happened to be written as a reciprocal.
21+
/// </summary>
22+
public sealed class IndeterminatePowerTest
23+
{
24+
private static Entity Limit(string expression, string destination, ApproachFrom side) =>
25+
expression.ToEntity().Limit("x", destination.ToEntity(), side).Simplify();
26+
27+
private static void AssertLimit(string expression, string destination, ApproachFrom side, string expected) =>
28+
Assert.Equal(expected.ToEntity().Evaled, Limit(expression, destination, side).Evaled);
29+
30+
/// <summary>
31+
/// 0^0. Over to the exponent, each of these is the limit of g * ln(f), which the rules
32+
/// for a vanishing-against-diverging product can take apart.
33+
/// </summary>
34+
[Theory]
35+
[InlineData("x ^ x", "0", ApproachFrom.Right, "1")]
36+
[InlineData("x ^ sin(x)", "0", ApproachFrom.Right, "1")]
37+
[InlineData("sin(x) ^ x", "0", ApproachFrom.Right, "1")]
38+
[InlineData("(1/x) ^ (1/x)", "+oo", ApproachFrom.Left, "1")]
39+
public void ZeroToTheZero(string expression, string destination, ApproachFrom side, string expected) =>
40+
AssertLimit(expression, destination, side, expected);
41+
42+
/// <summary>
43+
/// And 0^0 is not always 1, which is the whole reason it is indeterminate: the exponent
44+
/// decides, and here it decides on e.
45+
/// </summary>
46+
[Theory]
47+
[InlineData("x ^ (1 / ln(x))", "0", ApproachFrom.Right, "e")]
48+
[InlineData("x ^ (2 / ln(x))", "0", ApproachFrom.Right, "e ^ 2")]
49+
public void ZeroToTheZeroIsNotAlwaysOne(string expression, string destination, ApproachFrom side, string expected) =>
50+
AssertLimit(expression, destination, side, expected);
51+
52+
[Theory]
53+
[InlineData("x ^ (1/x)", "+oo", ApproachFrom.Left, "1")]
54+
[InlineData("x ^ (1 / ln(x))", "+oo", ApproachFrom.Left, "e")]
55+
[InlineData("(1/x) ^ x", "0", ApproachFrom.Right, "1")]
56+
public void InfinityToTheZero(string expression, string destination, ApproachFrom side, string expected) =>
57+
AssertLimit(expression, destination, side, expected);
58+
59+
/// <summary>
60+
/// A product of something vanishing with something diverging, written as the diverging
61+
/// factor over the reciprocal of the vanishing one. sin(x) * ln(x) is the one worth
62+
/// naming: it has no reciprocal factor at all, so the split that handles x * e^(-x) sees
63+
/// nothing in it.
64+
/// </summary>
65+
[Theory]
66+
[InlineData("sin(x) * ln(x)", "0", ApproachFrom.Right, "0")]
67+
[InlineData("x * ln(x)", "0", ApproachFrom.Right, "0")]
68+
[InlineData("sqrt(x) * ln(x)", "0", ApproachFrom.Right, "0")]
69+
[InlineData("x * cotan(x)", "0", ApproachFrom.Right, "1")]
70+
[InlineData("(1 - cos(x)) * cotan(x)", "0", ApproachFrom.Right, "0")]
71+
public void AVanishingFactorAgainstADivergingOne(string expression, string destination, ApproachFrom side, string expected) =>
72+
AssertLimit(expression, destination, side, expected);
73+
74+
/// <summary>
75+
/// The forms that already had a reading must keep it. 1^oo belongs to the second
76+
/// remarkable limit, which answers it more directly than going through the exponent
77+
/// would; x * e^(-x) is read by taking the reciprocal factor out, which gives the
78+
/// tidier x / e^x than inverting the other half would; and a base that tends to
79+
/// anything else raised to a vanishing power is not indeterminate at all.
80+
/// </summary>
81+
[Theory]
82+
[InlineData("(1 + x) ^ (1/x)", "0", ApproachFrom.Right, "e")]
83+
[InlineData("(1 + 2 * x) ^ (1/x)", "0", ApproachFrom.Right, "e ^ 2")]
84+
[InlineData("(1 + 1/x) ^ x", "+oo", ApproachFrom.Left, "e")]
85+
[InlineData("x * e ^ (-x)", "+oo", ApproachFrom.Left, "0")]
86+
[InlineData("x ^ 4 * e ^ (-x)", "+oo", ApproachFrom.Left, "0")]
87+
[InlineData("(2 + x) ^ (1/x)", "0", ApproachFrom.Right, "+oo")]
88+
[InlineData("x ^ x", "0", ApproachFrom.Left, "1")]
89+
[InlineData("ln(x) / x", "+oo", ApproachFrom.Left, "0")]
90+
public void EstablishedLimitsAreUnaffected(string expression, string destination, ApproachFrom side, string expected) =>
91+
AssertLimit(expression, destination, side, expected);
92+
93+
/// <summary>
94+
/// At an infinite destination there is only one direction to come from, so these need no
95+
/// side of their own.
96+
/// </summary>
97+
[Theory]
98+
[InlineData("x ^ (1 / ln(x))", "+oo", "e")]
99+
[InlineData("x ^ (1/x)", "+oo", "1")]
100+
public void TheFormsAtInfinityNeedNoSide(string expression, string destination, string expected) =>
101+
Assert.Equal(expected.ToEntity().Evaled,
102+
expression.ToEntity().Limit("x", destination.ToEntity()).Simplify().Evaled);
103+
104+
/// <summary>
105+
/// x^x at 0 is left with no two-sided limit, and that is right rather than a gap: x^x is
106+
/// not real for negative x, so there is no left-hand limit to agree with the right-hand
107+
/// one. The 1 that comes back from the left is the complex continuation, not a real
108+
/// limit, and the suite has pinned the two-sided answer as non-existent all along.
109+
/// </summary>
110+
[Fact]
111+
public void APowerThatIsNotRealOnOneSideHasNoTwoSidedLimit() =>
112+
Assert.Equal(MathS.NaN.Evaled,
113+
"x ^ x".ToEntity().Limit("x", 0).Simplify().Evaled);
114+
}
115+
}

0 commit comments

Comments
 (0)