An equation the solver cannot invert is left unsolved, not answered with no roots - #1504
Merged
Merged
Conversation
…ith no roots
x! = 6 was answered { }, a claim that it has no roots, and it has 3. The solver
isolates x by inverting the function around it, and for a factorial, a binomial
coefficient, mod, gcd, lcm, min, max, phi, prime, the valuation, a sum, a product, a
limit, a binder and a set with x inside it, the inversion had no way to write the
preimage and returned none. It now throws CannotInvertException, which the analytical
solvers catch at the level it was met, answering that equation as the set of x for
which it holds, the way a statement with no arm already was. Roots found beside it are
kept, and an equation left whole is given back as written.
A value such a function provably never takes still has no roots: the factorial is
the gamma function one along, which has no zeros, so x! = 0 is { } -- which is also
what an induction proof needs to read n!^2 as non-zero, and now reads for that reason.
Three tests pinned the empty set, one of them a wrong answer (x! - 1 has the roots 0
and 1); each is rewritten to what the answer means.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
This was referenced Sep 27, 2026
Closed
Member
|
Using exceptions as control flow is bad. Shouldn't this return null when non-invertible instead? |
Member
Author
|
Agreed. It threw because the inversion recurses through lazy sequences, and an exception got out of them without every node having to check. I'll make |
Member
Author
Rafael-SOWNet
added a commit
that referenced
this pull request
Sep 27, 2026
…1510) Invert and InvertNode return null where the preimage has no written form, and a node that inverts a child passes the child's null on. Where several inversions must all be written, the first null ends it and the rest are never computed, as the exception abandoned them: computing them anyway made SolveHard 3% slower. The analytical solvers answer the set-builder on null, at the level it was met, as they did on the exception, and the substitutions of the exponential and trigonometric solvers go through InvertEach, which declines on null. Review of #1504. Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura Co-authored-by: Claude Opus 5.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.
x! = 6was answered{ }, a claim that it has no roots, and it has 3. 2.5.0 does the same.The solver isolates
xby inverting the function around it. For some functions the inversion has no way to write the preimage, and it returned none, which the solver read as "no roots". Those functions are a factorial, a binomial coefficient,mod,gcd,lcm,min,max,phi,prime, the valuation, a sum, a product, a limit, a binder, and a set withxinside it. Several of these inversions carried comments calling the empty result "declining", but an empty result isn't a decline.Now such an inversion throws
CannotInvertException(internal). The analytical equation and set solvers catch it at the level where it was met, and answer that equation as the set ofxfor which it holds. The statement solver already does that for a statement it has no arm for, since "not the empty set, which claims there is no such x".x! = 6{ }{ x : x! = 6 }binomial(x, 2) = 3{ }on master{ x : binomial(x, 2) = 3 }x mod 3 = 1{ }{ x : x mod 3 = 1 }gcd(x, 4) = 2,max(x, 1) = 3,phi(x) = 4{ }xfor which it holdsprime(x) = 7{ }on master (2.5.0 readprime(x)asprime * x){ x : prime(x) = 7 }(x - 1) x! = 0{ 1 }{ 1 }x! = 0,arcsin(x) = 5{ }{ }A few properties of the change:
{ x : x! = 6 }, not the rearranged{ x : x! - 6 = 0 }.x! = 0stays{ }. That is also why(x - 1) x! = 0is exactly{ 1 }. The out-of-range inversions ofarcsinand its siblings are unchanged, since those empty results are true.DerivativefandIntegralfinversions are left alone. Their empty results come from Solving an equation that contains the unknown's derivative returns a non-solution #964's reasoning about independence, which this change doesn't touch.What it uncovered
InductionTest'sforall n in ZZ+ : product((2 k - 1) / (2 k), k, 1, n) = (2 n)! / (4^n (n!)^2)depended on the old answer. Its step needsn!^2 = 0to have no solution inZZ+, andSolveanswered{ }because it couldn't invert the factorial. The conclusion was true and the reason wasn't. With the factorial's zero-free inversion, the step is proved for the right reason, and the row isTrueagain.Three tests pinned the empty set, and each is rewritten to what the answer means:
SolveOneEquation'sx! - 1was pinned at no roots, and 0 and 1 are both roots.(x! provided x > 7) = 6expected{ }. That is true, but only because the solver claimedx! = 6has no roots. The answer now keeps the condition, and 3 is excluded by it.ModulusTest.SolvingIsNotClaimedpinned{ }on purpose, so that whoever changed it would notice. It now pins the unsolved set.This is also what the error functions of #1501 need. Without it,
erf(x) = 1/2would be answered{ }.Measured
Measured on this branch after its rebase onto #1502's merge:
🤖 Generated with Claude Code
https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura