Skip to content

Apply l'Hopital's rule at infinity too (#596) - #680

Merged
Happypig375 merged 2 commits into
ASC-Community:masterfrom
Rafael-SOWNet:feat/lhopital-at-infinity
Aug 4, 2026
Merged

Happypig375 merged 2 commits into
ASC-Community:masterfrom
Rafael-SOWNet:feat/lhopital-at-infinity

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

Addresses part of #596, and a class of limits that had no answer at all.

What was missing

l'Hopital's rule is already implemented, but ComputeLimit only reaches it on the branch for a finite destination. At infinity the call simply returns whatever ComputeLimitDivideEtImpera found, so the textbook competing-growth limits had nothing to catch them:

"ln(x) / x".ToEntity().Limit("x", "+oo")        // limit(ln(x) / x, x, +oo), and NaN once simplified
"x / e ^ x".ToEntity().Limit("x", "+oo")        //   "
"ln(x) / sqrt(x)".ToEntity().Limit("x", "+oo")  //   "
"e ^ x / (e ^ x + 1)".ToEntity().Limit("x", "+oo")  //   "

Three things had to be settled first

It has to stop. Differentiating both parts does not always make the quotient simpler — x / sqrt(x^2 + 1) turns into sqrt(x^2 + 1) / x, which turns back — so the recursion is bounded. The bound leaves room for the degree of a polynomial, since x^10 / e^x takes ten steps.

It must not make an answer worse. NaN asserts that a limit does not exist, which is a stronger claim than the rule can make when it has not settled. Its answer is only taken when it is not NaN; otherwise whatever was there before stands, unevaluated included.

It has to recognise the quotient. Two shapes were being turned away:

  • Differentiation does not produce quotients. d/dx ln(x)^2 is 2 * ln(x) * (1 / x) — a product with a quotient inside, not 2 * ln(x) / x — and only the second has a limit here. The derivative quotient is now simplified before use, and the domain condition that simplification leaves behind goes with it, since a Providedf is not a ContinuousNode and ComputeLimit turns it away unread. Limits already treat the expression as continuous; SimplifyAndComputeLimitToInfinity drops the same node for the same reason.
  • A product with a reciprocal factor is a quotient. This is the shape reported in Unexpected behavior of limits #596:
"(x ^ (5 - 1)) * e ^ (-x)".ToEntity().Limit("x", "+oo")  // was NaN, now 0
"x ^ 4 / e ^ x".ToEntity().Limit("x", "+oo")             // 0 either way

Effect

before after
lim x->+oo ln(x) / x unevaluated 0
lim x->+oo x / e^x unevaluated 0
lim x->+oo ln(x) / sqrt(x) unevaluated 0
lim x->+oo ln(x)^2 / x unevaluated 0
lim x->+oo e^x / (e^x + 1) unevaluated 1
lim x->+oo x / ln(x)^2 unevaluated +oo
lim x->+oo x^3 / ln(x) unevaluated +oo
lim x->+oo ln(ln(x)) / ln(x) unevaluated 0
lim x->+oo sqrt(x) / sqrt(x + 1) unevaluated 1
lim x->+oo x^10 / e^x unevaluated 0
lim x->+oo x^4 * e^(-x) unevaluated 0

Verification

  • 29 new tests, including three forms the rule cannot settle (x / sqrt(x^2 + 1), (x + sin(x)) / x, x^20 / e^x) asserting that each terminates and stays unevaluated rather than hanging or claiming NaN.
  • 3909 unit tests, 0 failed. 127 F# tests, 0 failed. Both target frameworks build.
  • Solver corpus 88/117 -> 91/117, still 0 wrong, 0 error, 0 timeout. lim:oo/oo goes to 4 out of 4.
  • A property checker over 151 expressions (1320 checks) is clean.

Not covered

e^x - x, sqrt(x^2 + x) - x and e^(x + e^(-x)) - e^x are still unevaluated — none of them is a quotient, and rewriting an oo - oo as one is a separate piece of work (#231, #353). They are left unevaluated rather than guessed.

The rule was only ever reached when the destination was finite, so the ordinary
competing-growth limits had nothing to catch them:

  lim x->+oo ln(x) / x        was unevaluated, and NaN once simplified
  lim x->+oo x / e^x                    "
  lim x->+oo ln(x) / sqrt(x)            "
  lim x->+oo e^x / (e^x + 1)            "

Three things had to be settled before the rule could be let loose there.

It has to stop. Differentiating both parts does not always make the quotient
simpler: x / sqrt(x^2 + 1) turns into sqrt(x^2 + 1) / x, which turns back, so
neither form ever settles. The recursion is bounded, at a depth that still leaves
room for the degree of a polynomial, since x^10 / e^x takes ten steps.

It has to leave a wrong answer no worse. NaN asserts that a limit does not exist,
which is a stronger claim than the rule can make when it has not settled, so the
rule's answer is only taken when it is not NaN; otherwise whatever was there
before stands, unevaluated included.

It has to recognise the quotient. Two shapes were being turned away:

- Differentiation does not produce quotients. d/dx ln(x)^2 is 2 * ln(x) * (1 / x),
  a product with a quotient inside rather than 2 * ln(x) / x, and only the second
  has a limit here, so the derivative quotient is simplified before it is used. The
  domain condition that simplification leaves behind is dropped with it: a
  Providedf is not a continuous node and would be turned away unread, and limits
  already treat the expression as continuous.
- A product with a reciprocal factor is a quotient. That is the shape in #596,
  where lim x->+oo x^4 * e^(-x) came out as NaN while x^4 / e^x gave 0.

Corpus 88/117 -> 91/117 with no wrong answers: lim:oo/oo goes to 4 out of 4 and
x / ln(x)^2 joins it. 3909 unit tests and 127 F# tests pass, and the property
checker's 1320 checks are clean.

Still out of reach and still left unevaluated rather than guessed: e^x - x,
sqrt(x^2 + x) - x and e^(x + e^(-x)) - e^x, none of which is a quotient.
@codecov-commenter

codecov-commenter commented Aug 3, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 88.46154% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.19%. Comparing base (90c00a8) to head (6a96767).
⚠️ Report is 51 commits behind head on master.

Files with missing lines Patch % Lines
...ath/Functions/Continuous/Limits/Transformations.cs 86.66% 4 Missing and 2 partials ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #680      +/-   ##
==========================================
- Coverage   80.99%   80.19%   -0.81%     
==========================================
  Files         155      156       +1     
  Lines       13687    12686    -1001     
  Branches     1957     2075     +118     
==========================================
- Hits        11086    10173     -913     
+ Misses       1990     1921      -69     
+ Partials      611      592      -19     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The rule as added in this branch could run for more than twenty seconds on
lim x -> +oo sqrt(x^2 - x) / x and on x^(3/2) * sqrt(1 + 1/x^2) / x^2, both of
which master answers, unevaluated, in under two hundred milliseconds.

The bound on the depth was not a bound on the work. Every step asks what the two
parts of its quotient tend to before differentiating them, and each of those is a
limit the rule may be applied to in turn, so the steps fan out rather than follow
one another and sixteen deep is far more than sixteen steps. Two conditions stop
the fan-out at its source: the rule declines a quotient it is already partway
through, since differentiating sqrt(x^2 - x) / x gives its reciprocal and
differentiating that gives the first back; and it declines one that has grown by
more than the derivative of a power or a logarithm accounts for, since a bigger
quotient makes the two questions each step asks harder than the one being
answered. The measured room is wide: a step on the way to an answer adds two
nodes and the most any was seen to add is six, against eighteen for the quotient
above. A cap on the total number of applications is left behind them as a
backstop.

Depth is now held for the whole of the rule rather than only for its recursive
call, so the two limits each step asks for are counted as well.

Both forms now terminate, the second of them in 86 ms, and the two are pinned.
Every limit the branch already answered is unchanged and no faster or slower:
x^10 / e^x still takes ten steps and 40 ms. 3911 tests, corpus 91/117 with
nothing wrong, in error or timing out.
@Happypig375
Happypig375 merged commit 8d4af56 into ASC-Community:master Aug 4, 2026
24 checks passed
@Rafael-SOWNet
Rafael-SOWNet deleted the feat/lhopital-at-infinity branch August 4, 2026 20:48
@Rafael-SOWNet Rafael-SOWNet mentioned this pull request Aug 4, 2026
2 of 3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants