Skip to content

Commit 7ea4508

Browse files
Rafael-SOWNetclaude
andcommitted
Give a one-sided limit the fallback the other paths have
A one-sided limit had nothing behind it. Where a two-sided or an infinite destination falls through to l'Hopital's rule and the rest, this path returned whatever the descent produced and stopped: if (side is ApproachFrom.Left or ApproachFrom.Right) return expr.ComputeLimitDivideEtImpera(x, dest, side); The descent can make an indeterminate form definite on the way down, by putting a part's own limit in place of the part. x * ln(x) at 0+ becomes 0 * ln(x), so 0 * -oo, so NaN -- and NaN is the claim that the limit does not exist, not that it was not found. sin(x) / x approached from either side came back the same way: the library said the most familiar limit in the subject did not exist. Where nothing above answered, this now moves x out to infinity and asks there. That is what ComputeLimitImpl does for a finite destination in any case, so it is the same question, put where l'Hopital's rule and the rest of the machinery for infinity live. Whatever the descent found is kept if this finds nothing better, so the change can only add answers. The substituted expression is simplified before being asked about, and not for tidiness: the destination is left behind as a term, and it is exactly that leftover which produces the NaN. (0 + 1/x) * ln(0 + 1/x) at infinity is NaN where (1/x) * ln(1/x) is 0. Measured over 26 one-sided limits worked out by hand: 13 correct before, 18 after; 12 wrong before, 7 after. Nothing that was right became wrong. Suite unchanged at 3m09s against 3m05s. Seven are still wrong, and this does not claim them: x^2 * ln(x), sqrt(x) * ln(x), x^x, (1 + x)^(1/x), (1 - cos(x)) / x^2, 1/x - 1/sin(x) and 1/ln(x) - 1/(x-1) from the right. Each needs the machinery at infinity to be stronger than it is, not a different bridge to it -- three of them answer already once the differences-at-infinity and Gruntz branches are in. (1 + x)^(1/x) at 0+ gives 1 rather than e both before and after, which is a separate defect on the two-sided-versus-one-sided path. Part of #209. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 7f1b4f2 commit 7ea4508

2 files changed

Lines changed: 108 additions & 1 deletion

File tree

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,33 @@ private static Entity ExpandLogarithm(Entity expr)
5151
expr = expr.Replace(ExpandLogarithm);
5252

5353
if (side is ApproachFrom.Left or ApproachFrom.Right)
54-
return expr.ComputeLimitDivideEtImpera(x, dest, side);
54+
{
55+
var oneSided = expr.ComputeLimitDivideEtImpera(x, dest, side);
56+
if (oneSided is { } found && (acceptNaN || found.Evaled != MathS.NaN))
57+
return oneSided;
58+
// The one-sided path is the only one with nothing behind it, and the descent
59+
// can make an indeterminate form definite on the way down: for x * ln(x) at 0+
60+
// it substitutes the first factor's own limit and then asks for 0 * -oo, which
61+
// is NaN. NaN is the claim that the limit does not exist, and here it is 0.
62+
//
63+
// Moving x out to infinity is what ComputeLimitImpl does for a finite
64+
// destination in any case, so this asks the same question in the place where
65+
// l'Hopital's rule and the rest of the machinery for infinity live -- none of
66+
// which this path can otherwise reach. Only where nothing above answered, and
67+
// whatever was found before is kept if this finds nothing better.
68+
// Simplified, and not only for tidiness: the substitution leaves the
69+
// destination behind as a term, and it is exactly that leftover which makes
70+
// the answer NaN. x * ln(x) at 0+ becomes (0 + 1/x) * ln(0 + 1/x), whose limit
71+
// at infinity comes back NaN, where the same expression written as
72+
// (1/x) * ln(1/x) comes back 0.
73+
if (dest.IsFinite
74+
&& ComputeLimit(expr.Substitute(x, side is ApproachFrom.Left ? dest - 1 / x : dest + 1 / x)
75+
.InnerSimplified,
76+
x, Real.PositiveInfinity) is { } byInfinity
77+
&& byInfinity.Evaled != MathS.NaN)
78+
return byInfinity;
79+
return oneSided;
80+
}
5581
if (side is ApproachFrom.BothSides)
5682
{
5783
expr = expr.Replace(a => TrivialTrigonometricReplacement(a, x));
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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 one-sided limit had nothing behind it. Where the two-sided and the infinite paths
17+
/// fall through to l'Hopital's rule, this one returned whatever the descent produced --
18+
/// and the descent can make an indeterminate form definite on the way down, by putting a
19+
/// part's own limit in place of the part. x * ln(x) at 0+ becomes 0 * ln(x), so 0 * -oo,
20+
/// so NaN, and NaN is the claim that the limit does not exist.
21+
/// </summary>
22+
public sealed class OneSidedLimitTest
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+
/// Every one of these came back NaN. sin(x) / x is the one worth naming: approached
32+
/// from either side it is 1, and the library said the limit did not exist.
33+
/// </summary>
34+
[Theory]
35+
[InlineData("sin(x) / x", "0", "1")]
36+
[InlineData("tan(x) / x", "0", "1")]
37+
[InlineData("x / sin(x)", "0", "1")]
38+
[InlineData("x * ln(x)", "0", "0")]
39+
public void FormsThatUsedToBeCalledNonExistentFromTheRight(string expression, string destination, string expected) =>
40+
AssertLimit(expression, destination, ApproachFrom.Right, expected);
41+
42+
[Theory]
43+
[InlineData("sin(x) / x", "0", "1")]
44+
[InlineData("x / sin(x)", "0", "1")]
45+
public void FormsThatUsedToBeCalledNonExistentFromTheLeft(string expression, string destination, string expected) =>
46+
AssertLimit(expression, destination, ApproachFrom.Left, expected);
47+
48+
/// <summary>
49+
/// The one-sided answers that were already right, kept. The fallback is only consulted
50+
/// where the descent returned nothing or NaN, so none of these should reach it at all.
51+
/// </summary>
52+
[Theory]
53+
[InlineData("1 / x", "0", ApproachFrom.Right, "+oo")]
54+
[InlineData("1 / x", "0", ApproachFrom.Left, "-oo")]
55+
[InlineData("1 / x ^ 2", "0", ApproachFrom.Right, "+oo")]
56+
[InlineData("1 / x ^ 2", "0", ApproachFrom.Left, "+oo")]
57+
[InlineData("ln(x)", "0", ApproachFrom.Right, "-oo")]
58+
[InlineData("ln(x) / x", "0", ApproachFrom.Right, "-oo")]
59+
[InlineData("e ^ (1/x)", "0", ApproachFrom.Right, "+oo")]
60+
[InlineData("e ^ (1/x)", "0", ApproachFrom.Left, "0")]
61+
[InlineData("1 / (x - 2)", "2", ApproachFrom.Right, "+oo")]
62+
[InlineData("1 / (x - 2)", "2", ApproachFrom.Left, "-oo")]
63+
[InlineData("(x ^ 2 - 1) / (x - 1)", "1", ApproachFrom.Right, "2")]
64+
[InlineData("(x ^ 2 - 1) / (x - 1)", "1", ApproachFrom.Left, "2")]
65+
[InlineData("x / (x + 1)", "0", ApproachFrom.Right, "0")]
66+
public void EstablishedOneSidedLimitsAreUnaffected(string expression, string destination, ApproachFrom side, string expected) =>
67+
AssertLimit(expression, destination, side, expected);
68+
69+
/// <summary>
70+
/// The fallback moves x out to infinity, which is what a finite destination does in any
71+
/// case. It must not be reached for a destination that is already infinite, where the
72+
/// substitution would mean nothing.
73+
/// </summary>
74+
[Theory]
75+
[InlineData("1 / x", "+oo", ApproachFrom.Right, "0")]
76+
[InlineData("x ^ 2 / (x ^ 2 + 1)", "+oo", ApproachFrom.Left, "1")]
77+
[InlineData("1 / x", "-oo", ApproachFrom.Left, "0")]
78+
public void AnInfiniteDestinationIsLeftAlone(string expression, string destination, ApproachFrom side, string expected) =>
79+
AssertLimit(expression, destination, side, expected);
80+
}
81+
}

0 commit comments

Comments
 (0)