Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,31 @@ and some are left unevaluated ([#2267](https://github.com/asc-community/AngouriM
| `"limitright(ln(sin(x)) / ln(cos(x)), x, 0)".ToEntity().Simplify()` | `-oo` | `+oo` |
| `"limit(ln(x^2) / ln(cos(x)), x, 0)".ToEntity().Simplify()` | `-oo` | `+oo`, by [#2274](https://github.com/asc-community/AngouriMath/issues/2274) |

### A limit nobody could take is not read as infinite, and an infinity has one side

**Silent: a quotient whose parts' limits were both left unevaluated is no longer answered by l'Hopital's rule, which
gave it a wrong value.** Whether a part diverges was read off whether an infinity occurs in its limit, and an unevaluated
`limit(sin(u), x, +oo)` holds one, as its destination. So `sin(u) / cos(u)`, with neither limit taken, was taken for
`oo / oo` and its derivatives' ratio answered `-cos(-1) / sin(-1)` for `tan(x^2 + x - 1)` from the left at 0, which is
0.642, where the limit is `tan(-1)`, -1.557. An unevaluated limit is now neither infinite nor finite. Some of these
limits are answered rightly, and some are left unevaluated. And a side asked for at `+oo` or `-oo`, which has only one,
is the limit there: it was taken by a path without the machinery for an infinity, and left unevaluated what it is. A
power whose base tends to a number other than 0 and whose exponent tends to 0 tends to 1, which is now answered
wherever the base's limit is found. One right answer of 2.6.0 is among those left unevaluated: `log(x^x, 3/2) - 1` from
the left at -1, which is `-1 - 0.129 i`, and which 2.7.0's previous changes had already turned into the wrong `-1`.

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"tan(x^2 + x - 1)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `-cos(-1) / sin(-1)` | `tan(-1)` |
| `"sin(x^2 + x - 1) / cos(x^2 + x - 1)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `-cos(-1) / sin(-1)` | `tan(-1)` |
| `"tan(x^2 - x - 1)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `-cos(-1) / sin(-1)` | `limitleft(tan(x ^ 2 - x - 1), x, 0)` |
| `"-x + ln(2*pi*x)/2".ToEntity().Limit("x", "+oo", ApproachFrom.Left)` | `limitleft(-x + ln(2 * pi * x) / 2, x, +oo)` | `-oo` |
| `"x - sqrt(x^2 + x)".ToEntity().Limit("x", "+oo", ApproachFrom.Left)` | `limitleft(x - sqrt(x ^ 2 + x), x, +oo)` | `-1/2` |
| `"e^x * x".ToEntity().Limit("x", "-oo", ApproachFrom.Right)` | `limitright(e ^ x * x, x, -oo)` | `0` |
| `"log(x, tanh(x)) ^ (x ^ (pi + x))".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `NaN`, in 10.7 s | `1` |
| `"tanh(log(x / (-1), 1/x))".ToEntity().Limit("x", -1, ApproachFrom.Right)` | `1` | unevaluated; the limit is -1.313 |
| `"log(x^x, 3/2) - 1".ToEntity().Limit("x", -1, ApproachFrom.Left)` | `ln(3/2) / ln(-1) - 1` | `limitleft(log(x ^ x, 3/2) - 1, x, -1)` |

### A value put in for a derivative's variable is where it is evaluated

**Silent: substituting for the variable of a derivative or an indefinite integral evaluates it there, where the value was
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,13 @@ internal static T WithinABudget<T>(int limits, System.Func<T> compute)
return null;
remainingLimits = remaining - 1;
}
// An infinity is approached from one side only, so a side asked for there is the
// limit itself -- and the two-sided path is where the machinery for an infinite
// destination lives. Taken one-sidedly, -x + ln(2 * pi * x) / 2 at +oo was left
// unevaluated where the limit is -oo. A complex infinity is not a real one and is
// turned away below as before.
if (side is not ApproachFrom.BothSides && dest is Real { IsFinite: false, IsNaN: false })
side = ApproachFrom.BothSides;
// The approach is stated for the whole computation, not only where the destination
// has been normalised to +oo. Everything below simplifies as it goes, and a rule that
// may only fire while the approach is in scope is otherwise declining through most of
Expand Down
39 changes: 35 additions & 4 deletions Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,15 @@ private static bool IsRealAlong(Entity expr, Variable x)
if (byStirling is null)
{
var baseLimit = EvalAssumingContinuous(@base.Limit(x, dest, side));
// A base tending to a number other than 0 is not an indeterminate form, and
// its limit is the limit's power: g * ln(f) tends to 0 whatever the branch of
// ln(f), which stays bounded on the cut too, so f^g tends to 1. Answered here
// rather than left to the descent, which asks the base's limit by its own
// reading and can decline what this one has just taken:
// lim x->0+ log(x, tanh(x))^(x^(pi + x)) is 1, and was answered only while
// an undecided base was taken for an infinite one.
if (IsFiniteNode(baseLimit) && baseLimit.Evaled is Complex { IsZero: false } finite && finite.IsFinite)
return Integer.One;
if (baseLimit != 0 && !IsInfiniteNode(baseLimit))
return null;
// Every route out of ln(f) runs through differentiating f, so a base this
Expand Down Expand Up @@ -789,6 +798,28 @@ private static bool IsInfiniteNode(Entity expr)
=> expr.ContainsNode("+oo") || expr.ContainsNode("-oo")
|| expr.Nodes.Any(node => node is Complex { IsFinite: false, IsNaN: false });

/// <summary>
/// Whether a limit's value is infinite and was decided to be: <see cref="IsInfiniteNode"/>,
/// but never for a value still holding a limit nobody could take.
/// </summary>
/// <remarks>
/// That limit is a question rather than a value, and the destination it carries is not
/// what it tends to: <c>limit(sin(u), x, +oo)</c> holds a <c>+oo</c> and says nothing
/// about diverging. l'Hopital's rule read sin(u) / cos(u) with both limits undecided as
/// oo/oo, differentiated it, and answered lim x->0- tan(x^2 + x - 1) with
/// -cos(-1) / sin(-1), 0.642, where it is tan(-1), -1.557. The rule is only true of the
/// forms it names, so it asks this.
/// <para/>
/// <see cref="IsInfiniteNode"/> is kept as it was for the rewrites that ask it whether
/// they are worth trying, which are equalities wherever they apply: f^g as e^(g ln f)
/// holds whatever f tends to, and an undecided base is exactly where it is worth asking.
/// </remarks>
private static bool DivergesDecidedly(Entity expr)
=> !HoldsAnUndecidedLimit(expr) && IsInfiniteNode(expr);

private static bool HoldsAnUndecidedLimit(Entity expr)
=> expr.Nodes.Any(node => node is Limitf);

/// <summary>
/// Whether what the machinery handed back is an indeterminate form rather than a value:
/// <c>0 / abs(0)</c>, <c>(+oo * 0)^2</c> -- a shape assembled out of the parts' limits
Expand Down Expand Up @@ -960,7 +991,7 @@ private static bool DivergesInMagnitude(Entity power, Variable x, Entity dest)
}

private static bool IsFiniteNode(Entity expr)
=> !IsInfiniteNode(expr) && expr != MathS.NaN;
=> !HoldsAnUndecidedLimit(expr) && !IsInfiniteNode(expr) && expr != MathS.NaN;

/// <summary>
/// What an inverse trigonometric function tends to where its argument grows without
Expand Down Expand Up @@ -1400,7 +1431,7 @@ private static bool IsIndeterminateQuotient(Entity numerator, Entity denominator
{
var above = EvalAssumingContinuous(numerator.Limit(x, dest, side));
var below = EvalAssumingContinuous(denominator.Limit(x, dest, side));
return above == 0 && below == 0 || IsInfiniteNode(above) && IsInfiniteNode(below);
return above == 0 && below == 0 || DivergesDecidedly(above) && DivergesDecidedly(below);
}

/// <summary>
Expand Down Expand Up @@ -1433,7 +1464,7 @@ private static bool IsIndeterminateQuotient(Entity numerator, Entity denominator
var limit = EvalAssumingContinuous(factor.Limit(x, dest, side));
if (vanishing is null && limit == 0)
vanishing = factor;
else if (diverging is null && IsInfiniteNode(limit))
else if (diverging is null && DivergesDecidedly(limit))
diverging = factor;
else
rest *= factor;
Expand Down Expand Up @@ -1511,7 +1542,7 @@ bool DoesNotSettle(Entity argument) => argument.ContainsNode(x)
if (expr is Divf(var num, var den))
if (EvalAssumingContinuous(num.Limit(x, dest, side)) is var numLimit && EvalAssumingContinuous(den.Limit(x, dest, side)) is var denLimit)
if (numLimit == 0 && denLimit == 0 ||
IsInfiniteNode(numLimit) && IsInfiniteNode(denLimit))
DivergesDecidedly(numLimit) && DivergesDecidedly(denLimit))
if (num is not Number && den is not Number)
if (num.ContainsNode(x) && den.ContainsNode(x)
&& !HasAStepThatJumpsNear(expr, x, dest, side))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// Copyright (c) 2019-2026 Angouri.
// AngouriMath is licensed under MIT.
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
// Website: https://am.angouri.org.
//

using AngouriMath.Core;
using Xunit;
using static AngouriMath.Entity;
using static AngouriMath.Entity.Number;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// l'Hopital's rule read a part whose limit nobody could take as infinite, because the unevaluated
/// <c>limit(sin(u), x, +oo)</c> holds a <c>+oo</c> -- its destination. <c>sin(u) / cos(u)</c> with both limits
/// undecided was taken for oo/oo and differentiated, and <c>tan(x^2 + x - 1)</c> at 0 from the left answered
/// <c>-cos(-1) / sin(-1)</c>, which is -cot(-1) = 0.642, where the limit is tan(-1) = -1.557. The same on 2.6.0.
/// Found by limitcheck at <c>DEPTH=3</c>, <c>SEED=26</c>; no issue was filed. And a side asked for at an infinity
/// is now the limit there, which the Stirling limits were reading through the same mistake.
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AnUndecidedLimitIsNotInfiniteTest
{
[Theory]
[InlineData("tan(x^2 + x - 1)", ApproachFrom.Left)]
[InlineData("sin(x^2 + x - 1) / cos(x^2 + x - 1)", ApproachFrom.Left)]
[InlineData("tan(x^2 + x - 1)", ApproachFrom.Right)]
public void IsTheTangent(string input, ApproachFrom side)
{
var limit = MathS.FromString(input).Limit("x", 0, side);
Assert.False(limit is Limitf, $"lim {side} x->0 {input} was left unevaluated");
AssertIsTheTangent(input, side, limit);
}

// These come back unevaluated: the parts' own limits from the left are not taken, and that is a decline, not a
// wrong answer. What they must not be is the quotient of the derivatives, as the first was before.
[Theory]
[InlineData("tan(x^2 - x - 1)", ApproachFrom.Left)]
[InlineData("tan(x^2 + x - 1)", ApproachFrom.BothSides)]
public void IsTheTangentOrUnevaluated(string input, ApproachFrom side)
{
var limit = MathS.FromString(input).Limit("x", 0, side);
if (limit is not Limitf)
AssertIsTheTangent(input, side, limit);
}

// An infinity has one side, and asking for it one-sidedly was left unevaluated where the limit is a value. The
// Stirling limits relied on reading the first of these, unevaluated, as infinite: it is -oo, by luck. The last
// two are a power whose base tends to a number and whose exponent tends to 0.
[Theory]
[InlineData("-x + ln(2*pi*x)/2", "+oo", ApproachFrom.Left, "-oo")]
[InlineData("e^x * x", "-oo", ApproachFrom.Right, "0")]
[InlineData("x - sqrt(x^2 + x)", "+oo", ApproachFrom.Left, "-1/2")]
[InlineData("x - sqrt(x^2 + x)", "+oo", ApproachFrom.Right, "-1/2")]
// Answered only while its base, asked at +oo from the left, came back undecided and was taken for infinite.
// Decided, the base tends to 1 and the power to 0, which is 1 and not an indeterminate form.
[InlineData("log(x, tanh(x)) ^ (x ^ (pi + x))", "0", ApproachFrom.Right, "1")]
[InlineData("(x - 3) ^ x", "0", ApproachFrom.Left, "1")]
public void IsTheLimit(string input, string destination, ApproachFrom side, string expected)
=> Assert.Equal(MathS.FromString(expected), MathS.FromString(input).Limit("x", destination, side));

private static void AssertIsTheTangent(string input, ApproachFrom side, Entity limit)
{
var difference = (limit - MathS.Tan(-1)).EvalNumerical();
Assert.True(difference is Complex value && Complex.Abs(value) is Real abs && abs < 1e-10,
$"lim {side} x->0 {input} = {limit}, which is not tan(-1)");
}
}
}
Loading