Read the quotient in the limit instead of gathering it in the simplifier (#802) - #805
Merged
Merged
Conversation
…ier (#802) `a^n / b^n` was gathered into `(a/b)^n` unconditionally, and that is false across the branch cuts: sqrt(2)/sqrt(-3) is -0.8165i where (2/-3)^(1/2) is +0.8165i. It is the quotient twin of #801 and was left out of that fix, because guarding it cost *answers*: `(x^2 + 1)^x / (x^2)^x` stopped having a limit at all, which is the whole of #739 and #740. That framing was wrong, and this is the correction. The gathering was never the point -- #740 wanted it so the limit machinery could read a 1^oo out of a quotient, and its own test comment says so. The right place for the rewrite is therefore the limit reader, not the simplifier, because that is the only place where it can be justified: a limit needs the identity to hold in a neighbourhood of the destination, so requiring both bases to be eventually positive there is enough, and there is a destination to check that against. In the simplifier there is no destination and nothing to check, which is why it was unconditional. So ApplySecondRemarkable now recognises `a^n / b^n` itself, and the simplifier's rule takes the same guard its whole family carries. Every limit survives: (x^2 + 1)^x / (x^2)^x 1 unchanged (x^3 + 1)^x / (x^3)^x 1 unchanged (x - 5)^x / x^x e^(-5) unchanged (sqrt(x) + 1)^x / sqrt(x)^x unchanged sqrt(x) / sqrt(y) sqrt(x/y) -> unchanged, and correct at x=2, y=-3 Eleven tests in PowerQuotientGatheringTest moved from asserting the *mechanism* -- a single power in Simplify's output -- to asserting the *outcome* it existed for, which is that the limit is answered. That distinction is the whole content of this change, and the tests that pinned the mechanism were pinning something unsound. The four with symbolic bases no longer gather at all and now assert that simplifying does not change their value, since nothing can say a symbolic quotient stays on the principal branch. The test recording #802 as open is flipped to assert it stays fixed, which is what it was written to do. Unit 5543 pass 0 fail; F# 130/130; casbench 113/117 0 wrong; rootcheck 596/596; simpsweep 10463/10463; propcheck 0 failures. Co-Authored-By: Claude Opus 5 (1M context) <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.
Fixes #802 — the quotient twin of #801, and without the cost I said it would have.
a^n / b^nwas gathered into(a/b)^nunconditionally, which is false across the branch cuts:sqrt(2)/sqrt(-3)is-0.8165iwhere(2/-3)^(1/2)is+0.8165i.I left this out of #801 because guarding it removed limit answers —
(x^2 + 1)^x / (x^2)^xstopped having a limit at all, which is the whole of #739 and #740. That framing was wrong, and this is the correction.The gathering was never the point
#740 wanted it so the limit machinery could read a
1^ooout of a quotient — its own test comment says exactly that. So the rewrite belongs in the limit reader, not the simplifier, because that is the only place it can be justified:ApplySecondRemarkablenow recognisesa^n / b^nitself, and the simplifier's rule takes the same guard the rest of its family carries.Every limit survives
(x^2 + 1)^x / (x^2)^x1(x^3 + 1)^x / (x^3)^x1(x - 5)^x / x^xe^(-5)(sqrt(x) + 1)^x / sqrt(x)^xsqrt(x) / sqrt(y)sqrt(x/y)Eleven tests moved from the mechanism to the outcome
PowerQuotientGatheringTestasserted thatSimplifyprints a single power. It no longer does, and should not — those assertions were pinning something unsound. They now assert that the limit is answered, which is what the mechanism existed for. The four cases with symbolic bases no longer gather at all, and assert instead that simplifying does not change their value, since nothing can say a symbolic quotient stays on the principal branch.The test that recorded #802 as open is flipped to assert it stays fixed — which is what it was written to do.
Harnesses
casbench113/117, 0 wrong;rootcheck596/596;simpsweep10463/10463;propcheck0 failures🤖 Generated with Claude Code