Repository navigation
Squeeze a bounded factor against a vanishing one (#723) - #724
Merged
Merged
Conversation
Every rule in the limits pipeline reads a limit as a value: the descent
substitutes each part's own limit, l'Hopital's rule wants a determinate
quotient, Gruntz compares rates of growth. sin(x) has no limit at infinity,
so each of them correctly declined and the product was left indeterminate
in the shape (no limit) * 0 -- which is why the squeeze theorem's most
standard example, lim x->+oo sin(x) / x, came back unevaluated.
The theorem does not need the factor's limit. It needs the factor to be
bounded, which is a weaker fact and one that can be read off the shape:
a sine, a cosine and a sign never exceed 1, an arctangent and an
arccotangent stay within an interval of angles, and sums, products and
moduli of bounded things are bounded. That predicate plus one case for a
product and one for a quotient is the whole of it, and it is deliberately
not "has a finite limit" -- that is the case the machinery already covers,
and asking for it here would repeat work while missing the factors this
exists for.
Boundedness of a sine is a fact about a real argument, and this library
reads a free variable as complex: sin(i * t) is i * sinh(t), which grows
without bound. So the argument must be established real first, which
leaves lim x->+oo sin(a * x) / x unanswered -- correctly, since it is not
0 for every a. That limit is the example in Limit's own documentation.
A second, smaller rule answers the other half: a sine or cosine whose
argument grows without bound takes every value in its range infinitely
often and settles on none, so its limit does not exist. NaN says that,
where leaving the node unevaluated says only that no rule found one. It is
asked after the theorem and not before it -- asked first it would settle
sin(x) / x as (no limit) / (+oo).
Two tests changed rather than broke. Both pinned a bare sin(x) at infinity
as unevaluated, and one says in its own summary that the answer must not
become NaN "since NaN asserts that the limit does not exist rather than
that it was not found". The principle is right and is why the case moved
rather than being loosened: nothing there established non-existence, so
claiming it there would have been a guess, and a sine's range does
establish it. The other forms in both tests -- (x + sin(x)) / x,
x * sin(x), x * cos(x) -- are still unevaluated and stay where they are.
lim x->+oo sin(x) / x unevaluated -> 0
lim x->0 x * sin(1 / x) unevaluated -> 0
lim x->+oo sin(x) unevaluated -> NaN
lim x->0 sin(1 / x) unevaluated -> NaN
lim x->+oo sin(a * x) / x unevaluated, unchanged
29 regression cases, tests 4807 -> 4834 passing with none failing, F# 130
of 130, corpus 112/117 with 0 wrong, 0 error and 0 timeout, suite 3m33s ->
3m28s.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #723.
What was wrong
Every rule in the limits pipeline reads a limit as a value: the descent substitutes each part's own limit, l'Hopital's rule wants a determinate quotient, Gruntz compares rates of growth.
sin(x)has no limit at infinity, so each of them correctly declines and the product is left indeterminate in the shape(no limit) * 0. That is why the squeeze theorem's most standard example came back unevaluated:The rule
The theorem does not need the factor's limit. It needs the factor to be bounded, which is a weaker fact and one that can be read off the shape: a sine, a cosine and a sign never exceed 1; an arctangent and an arccotangent stay within an interval of angles; sums, products and moduli of bounded things are bounded. That predicate, plus one case for a product and one for a quotient, is the whole of it.
It is deliberately not "has a finite limit". That is the case the rest of the machinery already covers, and asking for it here would repeat work while missing the factors this exists for — the ones with no limit at all.
Both rules live in
LimitSolversand are reached fromSimplifyAndComputeLimitToInfinity, which every destination and side is normalised into, so one statement coversx -> +oo,x -> -ooand either side of a finite point.Boundedness is a fact about a real argument
This library reads a free variable as complex, and
sin(i * t)isi * sinh(t), which grows without bound. So the argument is established real before anything is claimed, which leavesunanswered — correctly, since it is not 0 for every
a. That limit is the example inLimit's own documentation, and it prints exactly as it did.The other half
A sine or cosine whose argument grows without bound takes every value in its range infinitely often and settles on none, so its limit does not exist.
NaNsays that; leaving the node unevaluated says only that no rule found one. It is asked after the theorem and not before — asked first it would settlesin(x) / xas(no limit) / (+oo).Two tests changed rather than broke
LimitAtInfinityTest.AFormThatCannotBeSettledTerminatesWithoutClaimingAnAnswerandGruntzTest.AnOscillationIsDeclinedRatherThanGuessedAtboth pinned a baresin(x)at infinity as unevaluated, and the first says in its own summary that the answer must not become NaN "since NaN asserts that the limit does not exist rather than that it was not found."That principle is right, and it is why the case moved rather than the assertion being loosened. Nothing in either test established non-existence, so claiming it there would have been a guess; a sine's range does establish it. The other forms in both tests —
(x + sin(x)) / x,x * sin(x),x * cos(x)— are still unevaluated and stay where they are. Gruntz still declines all three; a different rule now answers one of them.Measured
lim x->+oo sin(x) / x0lim x->0 x * sin(1 / x)0lim x->+oo sin(x) * e^(-x)0lim x->+oo sin(x)NaNlim x->0 sin(1 / x)NaNlim x->+oo sin(a * x) / xlim x->0 sin(x) / x11lim x->+oo (1 + 1/x)^xee29 regression cases in a new
BoundedTimesVanishingTest, covering the theorem in both shapes, the answers that were already given by the descent, the complex-argument guard and the arguments that settle rather than oscillate.Unit tests 4807 -> 4834 passing with none failing; F# 130 of 130; the 117-problem solver corpus stays at 112 solved with 0 wrong, 0 error and 0 timeout; suite 3m33s -> 3m28s.
🤖 Generated with Claude Code