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
15 changes: 15 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,21 @@ Two-sided, each side is now taken with its own value, and `floor(x)^x` at 0 is 1
| `"floor(x)^tanh(x)".ToEntity().Limit("x", 0, ApproachFrom.Right)` | unevaluated | `0` |
| `"floor(x)^x".ToEntity().Limit("x", 0)` | unevaluated | `NaN`, the sides differ |

### A function of a quotient tending to 0/0 has the value at the quotient's limit

**Silent: a different answer.** A function of a quotient took the quotient's limit from the descent alone, which reads
0/0 or oo/oo off the two parts as NaN -- the claim that there is no limit -- where only the descent cannot tell. The
quotient's whole limit is asked now, once, through the remarkable limits and l'Hopital's rule, and only a value it
gives is taken. No issue was filed.

| Input | Was (2.6.0) | Now |
|---|---|---|
| `limit((sin(x)/x)^2, x, 0)` | `NaN` | `1` |
| `limit(exp(sin(x)/x), x, 0)` | `NaN` | `e` |
| `limit(sin(sin(x)/x), x, 0)` | `NaN` | `sin(1)` |
| `limit(exp((cos(x) - 1)/x^2), x, 0)` | `NaN` | `e ^ (-1/2)` |
| `limitright(exp(sin(x)/x), x, 0)` | unevaluated | `e` |

### A two-sided limit of an expression real on both sides is where its sides agree

**Silent: a two-sided limit that was `NaN` although both sides agree has their value, where the expression is real on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,55 @@ partial record Divf
// which side the divisor vanishes from, and so comes back NaN. Only where
// nothing above answered, and whatever it did answer is kept if this finds
// nothing better.
return DivergesAtAVanishingDivisor(Dividend, Divisor, x, dist, side) ?? substituted;
if (DivergesAtAVanishingDivisor(Dividend, Divisor, x, dist, side) is { } diverges)
return diverges;
// An indeterminate form, 0/0 or oo/oo, read off the parts is NaN, which says there
// is no limit, where the descent only cannot tell: sin(x)/x at 0 is 1, which the
// whole limit reaches through the first remarkable limit, and a function of it --
// (sin(x)/x)^2, exp(sin(x)/x), exp(ln(cos(x))/x^2) -- came out NaN, its argument
// asked only of the descent. The whole limit of the quotient is asked once, and
// only a value is taken, and as the number where the form it came back in holds the
// infinity it was taken at: -4/9 * 0 * (1 - (1 + (1/+oo)^2)^(1/3)), handed on as
// written, sent a definite integral astray. Folded, it is 0, and ln(2) * 2^(1/+oo) is
// ln(2), not its decimal. Where it has no
// value, what was answered is kept.
if (substituted?.Evaled == MathS.NaN && IsAnIndeterminateQuotient(dividend, divisor)
&& AsTheWholeLimit(this, x, dist, side) is { } whole && whole.Evaled is Number { IsNaN: false } value)
return whole.InnerSimplified is var folded && folded.Nodes.Any(static node => node is Number { IsFinite: false })
? value : folded;
return substituted;
}
}
}

/// <summary>Whether two parts' limits make a quotient 0/0 or oo/oo.</summary>
private static bool IsAnIndeterminateQuotient(Entity dividend, Entity divisor)
=> (dividend.Evaled, divisor.Evaled) switch
{
(Complex { IsZero: true }, Complex { IsZero: true }) => true,
(Number { IsFinite: false, IsNaN: false }, Number { IsFinite: false, IsNaN: false }) => true,
_ => false,
};

/// <summary>The quotient whose whole limit is being asked from inside the descent, so that no other is asked inside it.</summary>
[System.ThreadStatic] private static HashSet<Entity>? quotientsAskedWhole;

private static Entity? AsTheWholeLimit(Entity quotient, Variable x, Entity dist, ApproachFrom side)
{
// One level: a whole limit asked from here descends again, and a retry inside it asked another, so that a
// definite integral's antiderivative at its bounds -- 1/((1 + x^2)^(1/3) (9 + x^2)) across 0 -- went from four
// seconds to past two minutes and was left unevaluated.
// Nor under a caller's budget of limits, which every limit asked here spends: fifty asked on the side of a
// definite integral's bounds left the bounds none, and the integral unevaluated.
if (Functions.Algebra.LimitFunctional.UnderALimitBudget)
return null;
quotientsAskedWhole ??= new();
if (quotientsAskedWhole.Count > 0 || !quotientsAskedWhole.Add(quotient))
return null;
try { return Functions.Algebra.LimitFunctional.ComputeLimit(quotient, x, dist, side); }
finally { quotientsAskedWhole.Remove(quotient); }
}

partial record Modf
{
internal override Entity? ComputeLimitDivideEtImpera(Variable x, Entity dist, ApproachFrom side)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ private static Entity ExpandLogarithm(Entity expr)
/// </summary>
[System.ThreadStatic] private static int? remainingLimits;

/// <summary>Whether a caller has set a budget of limits, which a limit asked on the side would spend.</summary>
internal static bool UnderALimitBudget => remainingLimits is not null;

/// <summary>
/// <paramref name="compute"/> with at most <paramref name="limits"/> limits taken in all, for a caller that has a
/// use only for a limit it can have cheaply, and leaves the question open otherwise.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// 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;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// A function of a quotient that tends to 0/0 took the quotient's limit from the descent alone, which reads 0/0 off
/// the two parts as NaN -- the claim that there is no limit -- where only the descent cannot tell: <c>sin(x)/x</c> at 0
/// is 1, and <c>(sin(x)/x)^2</c> and <c>exp(sin(x)/x)</c> were NaN two-sided and unevaluated from either side. The
/// quotient's whole limit is asked now, once, and only its value is taken. Found while checking a limit
/// limitcheck reported; no issue was filed.
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AFunctionOfAnIndeterminateQuotientTest
{
[Theory]
[InlineData("(sin(x)/x)^2", ApproachFrom.BothSides, "1")]
[InlineData("exp(sin(x)/x)", ApproachFrom.BothSides, "e")]
[InlineData("exp(sin(x)/x)", ApproachFrom.Right, "e")]
[InlineData("sin(sin(x)/x)", ApproachFrom.BothSides, "sin(1)")]
[InlineData("exp((cos(x) - 1)/x^2)", ApproachFrom.BothSides, "e^(-1/2)")]
[InlineData("sqrt(sin(x)/x)", ApproachFrom.Right, "1")]
// As before.
[InlineData("sin(x)/x", ApproachFrom.BothSides, "1")]
[InlineData("exp(x/x)", ApproachFrom.BothSides, "e")]
public void HasTheValueOfTheFunctionAtTheQuotientsLimit(string input, ApproachFrom side, string expected)
{
var limit = MathS.FromString(input).Limit("x", 0, side);
Assert.IsAssignableFrom<Number>(limit.Evaled);
var difference = (limit - MathS.FromString(expected)).EvalNumerical();
Assert.True(difference.Abs() < (Number.Real)1e-30, $"the limit of {input} at 0 is {limit}, where it is {expected}");
}
}
}
Loading