Skip to content

Commit 6c1f6b4

Browse files
Say which infinity a quotient with a vanishing divisor tends to (#700)
The descent puts each part's own limit in place of the part, and for this shape that throws away the only thing that decides the answer: cos(x) / sin(x) at 0 becomes 1 / 0, which says nothing about which side the divisor vanishes from and so comes back NaN -- the claim that the limit does not exist, where on the right it is +oo and on the left -oo. Only 1 / x and the other quotients of polynomials escaped it, because those the solvers read outright once x has been moved out to infinity, and it is that accident which made the gap easy to miss. The side the divisor vanishes from is read off its first derivative that does not vanish with it. Where g(a) = 0 and the first non-vanishing derivative there is the k-th, g(x) has the sign of g_k(a) * (x - a)^k near a, which is the sign of g_k(a) on the right and that times (-1)^k on the left. That covers the first order, where sin(x) and e^x - 1 and x - a live, and the second, where 1 - cos(x) does -- and being even, that one looks the same from both sides, so 1 / (1 - cos(x)) now has a two-sided limit of +oo as well. Nothing is claimed unless a derivative comes out finite and non-zero at the point. An expression that is not differentiable there, or whose derivative diverges as sqrt(x)'s does, is left alone rather than guessed at: reading the sign wrongly would answer +oo where the truth is -oo, which is worse than not answering. Four derivatives is as far as it looks. Reached only where the substitution above answered nothing or NaN, so it costs nothing on any path that already worked, and whatever that answered is kept if this finds nothing better. 17 of the 35 new tests fail without it. Corpus unchanged at 101/117 with 0 wrong; suite 4044 passed, 0 failed.
1 parent b845b05 commit 6c1f6b4

3 files changed

Lines changed: 186 additions & 1 deletion

File tree

‎Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,15 @@ partial record Divf
9494
({ } bas, { IsFinite: true } lim2) => (bas, lim2),
9595
_ => (Dividend, Divisor)
9696
};
97-
return ComputeLimitImpl(New(dividend, divisor), x, dist, side);
97+
var substituted = ComputeLimitImpl(New(dividend, divisor), x, dist, side);
98+
if (substituted is { } found && found.Evaled != MathS.NaN)
99+
return found;
100+
// Putting the two parts' limits in place of the parts loses the one thing
101+
// that decides a quotient whose divisor vanishes: 1 / 0 says nothing about
102+
// which side the divisor vanishes from, and so comes back NaN. Only where
103+
// nothing above answered, and whatever it did answer is kept if this finds
104+
// nothing better.
105+
return DivergesAtAVanishingDivisor(Dividend, Divisor, x, dist, side) ?? substituted;
98106
}
99107
}
100108
}

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,68 @@ private static bool DivergesInMagnitude(Entity power, Variable x, Entity dest)
8383
private static bool IsFiniteNode(Entity expr)
8484
=> !IsInfiniteNode(expr) && expr != MathS.NaN;
8585

86+
/// <summary>
87+
/// How many derivatives of the divisor to take looking for the order at which it
88+
/// vanishes. A divisor that is still flat after four of them is one whose sign this has
89+
/// no cheap reading of, and reading it wrongly would answer +oo where the truth is -oo,
90+
/// which is worse than not answering. The forms this is for vanish at the first order
91+
/// (sin(x), e^x - 1, x - a) or the second (1 - cos(x), x^2).
92+
/// </summary>
93+
private const int MaxVanishingOrder = 4;
94+
95+
/// <summary>
96+
/// The infinity a quotient tends to when its divisor vanishes and its dividend does not,
97+
/// or <see langword="null"/> where that is not the shape or the side the divisor
98+
/// vanishes from cannot be read off.
99+
/// </summary>
100+
/// <remarks>
101+
/// The descent puts each part's own limit in place of the part, and for this shape that
102+
/// throws away the only thing that decides the answer. <c>cos(x) / sin(x)</c> at 0
103+
/// becomes <c>1 / 0</c>, which is NaN -- the claim that the limit does not exist -- where
104+
/// on the right it is +oo and on the left -oo.
105+
/// <para/>
106+
/// The side the divisor vanishes from is read off its first derivative that does not
107+
/// vanish with it. Where <c>g(a) = 0</c> and the first non-vanishing derivative there is
108+
/// the k-th, <c>g(x)</c> has the sign of <c>g_k(a) * (x - a)^k</c> near a, which is the
109+
/// sign of <c>g_k(a)</c> on the right and that times <c>(-1)^k</c> on the left. Nothing
110+
/// is claimed unless a derivative comes out finite and non-zero at the point: an
111+
/// expression that is not differentiable there, or whose derivative diverges as
112+
/// <c>sqrt(x)</c>'s does, is left alone.
113+
/// </remarks>
114+
internal static Entity? DivergesAtAVanishingDivisor(Entity dividend, Entity divisor, Variable x, Entity dest, ApproachFrom side)
115+
{
116+
if (side is not (ApproachFrom.Left or ApproachFrom.Right) || !dest.IsFinite || !divisor.ContainsNode(x))
117+
return null;
118+
if (EvalAssumingContinuous(divisor.Limit(x, dest, side)) is not Real { IsZero: true })
119+
return null;
120+
// A dividend that vanishes too makes the quotient indeterminate rather than
121+
// divergent, and one that diverges is a different question again. Only a dividend
122+
// with a definite non-zero size leaves the divisor to decide the answer.
123+
if (EvalAssumingContinuous(dividend.Limit(x, dest, side)) is not Real { IsFinite: true, IsZero: false } dividendLimit)
124+
return null;
125+
126+
var derivative = divisor;
127+
for (var order = 1; order <= MaxVanishingOrder; order++)
128+
{
129+
MultithreadingFunctional.ExitIfCancelled();
130+
derivative = derivative.Differentiate(x).Simplify();
131+
var atThePoint = EvalAssumingContinuous(derivative.Substitute(x, dest).InnerSimplified);
132+
if (atThePoint is not Real { IsFinite: true } value)
133+
return null;
134+
if (value.IsZero)
135+
continue;
136+
// (x - a)^k is positive on the right whatever k is, and on the left it takes the
137+
// sign of (-1)^k, so approaching from the left at an odd order turns the sign of
138+
// the derivative around and nothing else does.
139+
var turnsAround = side is ApproachFrom.Left && order % 2 != 0;
140+
var divisorIsPositive = value.IsNegative == turnsAround;
141+
return dividendLimit.IsNegative == divisorIsPositive
142+
? Real.NegativeInfinity
143+
: Real.PositiveInfinity;
144+
}
145+
return null;
146+
}
147+
86148
/// <summary>
87149
/// How deep the rule may be applied to one limit. Differentiating both parts does not
88150
/// always make the quotient simpler -- x / sqrt(x^2 + 1) turns into sqrt(x^2 + 1) / x,
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+
/// A quotient whose divisor vanishes while its dividend does not. The descent puts each
17+
/// part's own limit in place of the part, and 1 / 0 says nothing about which side the
18+
/// divisor vanishes from, so cos(x) / sin(x) at 0 came out as NaN -- the claim that the
19+
/// limit does not exist -- where on the right it is +oo and on the left -oo. Only 1 / x and
20+
/// the other quotients of polynomials escaped it, because those the solvers read outright
21+
/// once x has been moved out to infinity.
22+
/// </summary>
23+
public sealed class VanishingDenominatorTest
24+
{
25+
private static void AssertLimit(string expression, string destination, ApproachFrom side, string expected) =>
26+
Assert.Equal(
27+
expected.ToEntity().Evaled,
28+
expression.ToEntity().Limit("x", destination.ToEntity(), side).Evaled);
29+
30+
/// <summary>
31+
/// A divisor whose first derivative does not vanish at the point takes the sign of that
32+
/// derivative on the right and the opposite on the left.
33+
/// </summary>
34+
[Theory]
35+
[InlineData("1 / sin(x)", "0", ApproachFrom.Right, "+oo")]
36+
[InlineData("1 / sin(x)", "0", ApproachFrom.Left, "-oo")]
37+
[InlineData("-1 / sin(x)", "0", ApproachFrom.Right, "-oo")]
38+
[InlineData("-1 / sin(x)", "0", ApproachFrom.Left, "+oo")]
39+
[InlineData("cos(x) / sin(x)", "0", ApproachFrom.Right, "+oo")]
40+
[InlineData("cos(x) / sin(x)", "0", ApproachFrom.Left, "-oo")]
41+
[InlineData("1 / tan(x)", "0", ApproachFrom.Right, "+oo")]
42+
[InlineData("1 / tan(x)", "0", ApproachFrom.Left, "-oo")]
43+
[InlineData("-2 / (e ^ x - 1)", "0", ApproachFrom.Right, "-oo")]
44+
[InlineData("-2 / (e ^ x - 1)", "0", ApproachFrom.Left, "+oo")]
45+
[InlineData("2 / ln(x)", "1", ApproachFrom.Right, "+oo")]
46+
[InlineData("2 / ln(x)", "1", ApproachFrom.Left, "-oo")]
47+
public void AFirstOrderZeroTurnsTheSignAroundOnTheLeft(string expression, string destination, ApproachFrom side, string expected) =>
48+
AssertLimit(expression, destination, side, expected);
49+
50+
/// <summary>
51+
/// An even order does not: (x - a)^k is positive on both sides of a. 1 - cos(x) is flat
52+
/// at 0 and curves upward, so 1 / (1 - cos(x)) is +oo whichever way 0 is approached --
53+
/// and so the two-sided limit exists as well.
54+
/// </summary>
55+
[Theory]
56+
[InlineData("1 / (1 - cos(x))", "0", ApproachFrom.Right, "+oo")]
57+
[InlineData("1 / (1 - cos(x))", "0", ApproachFrom.Left, "+oo")]
58+
[InlineData("3 / (sin(x) - 1)", "pi / 2", ApproachFrom.Right, "-oo")]
59+
[InlineData("3 / (sin(x) - 1)", "pi / 2", ApproachFrom.Left, "-oo")]
60+
public void AnEvenOrderZeroLooksTheSameFromEitherSide(string expression, string destination, ApproachFrom side, string expected) =>
61+
AssertLimit(expression, destination, side, expected);
62+
63+
/// <summary>
64+
/// And an odd order beyond the first turns it around again.
65+
/// </summary>
66+
[Theory]
67+
[InlineData("1 / x ^ 3", "0", ApproachFrom.Right, "+oo")]
68+
[InlineData("1 / x ^ 3", "0", ApproachFrom.Left, "-oo")]
69+
[InlineData("cos(x) / (x - 1) ^ 3", "1", ApproachFrom.Right, "+oo")]
70+
[InlineData("cos(x) / (x - 1) ^ 3", "1", ApproachFrom.Left, "-oo")]
71+
public void AnOddOrderTurnsItAroundAgain(string expression, string destination, ApproachFrom side, string expected) =>
72+
AssertLimit(expression, destination, side, expected);
73+
74+
[Theory]
75+
[InlineData("1 / (1 - cos(x))", "0", "+oo")]
76+
[InlineData("1 / x ^ 2", "0", "+oo")]
77+
public void ATwoSidedLimitFollowsWhereBothSidesAgree(string expression, string destination, string expected) =>
78+
Assert.Equal(
79+
expected.ToEntity().Evaled,
80+
expression.ToEntity().Limit("x", destination.ToEntity()).Evaled);
81+
82+
/// <summary>
83+
/// Where the two sides disagree there is no two-sided limit, and NaN is the right answer
84+
/// rather than the absence of one. These have to keep saying so.
85+
/// </summary>
86+
[Theory]
87+
[InlineData("1 / sin(x)", "0")]
88+
[InlineData("cos(x) / sin(x)", "0")]
89+
[InlineData("1 / x", "0")]
90+
[InlineData("1 / x ^ 3", "0")]
91+
public void TwoSidesThatDisagreeStillHaveNoLimit(string expression, string destination) =>
92+
Assert.Equal(
93+
MathS.NaN.Evaled,
94+
expression.ToEntity().Limit("x", destination.ToEntity()).Evaled);
95+
96+
/// <summary>
97+
/// Nothing is claimed where the shape is not this one. A dividend that vanishes too
98+
/// makes the quotient indeterminate rather than divergent, and a divisor that is not
99+
/// differentiable at the point -- or whose derivative diverges there, as sqrt(x)'s does
100+
/// -- is left alone.
101+
/// </summary>
102+
[Theory]
103+
[InlineData("(x ^ 2 - 1) / (x - 1)", "1", ApproachFrom.Left, "2")]
104+
[InlineData("1 / x", "0", ApproachFrom.Right, "+oo")]
105+
[InlineData("1 / x", "0", ApproachFrom.Left, "-oo")]
106+
[InlineData("1 / (x - 2)", "2", ApproachFrom.Right, "+oo")]
107+
[InlineData("1 / (x - 2)", "2", ApproachFrom.Left, "-oo")]
108+
[InlineData("(x ^ 2 - 1) / (x - 1)", "1", ApproachFrom.Right, "2")]
109+
[InlineData("x / (x + 1)", "0", ApproachFrom.Right, "0")]
110+
[InlineData("1 / x", "+oo", ApproachFrom.Left, "0")]
111+
[InlineData("x ^ 2 / (x ^ 2 + 1)", "+oo", ApproachFrom.Left, "1")]
112+
public void EstablishedLimitsAreUnaffected(string expression, string destination, ApproachFrom side, string expected) =>
113+
AssertLimit(expression, destination, side, expected);
114+
}
115+
}

0 commit comments

Comments
 (0)