Skip to content

An inversion with no written preimage returns null, not an exception - #1510

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
invert-returns-null
Sep 27, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
invert-returns-null

Conversation

@Rafael-SOWNet

@Rafael-SOWNet Rafael-SOWNet commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

From the review of #1504 (comment). An inversion that couldn't write its preimage threw CannotInvertException, and the analytical solvers caught it. It now returns null.

  • Invert and InvertNode return IEnumerable<Entity>?. null means the preimage has no written form. An empty sequence still means there are provably no roots.
  • A node that inverts a child passes the child's null on. Where several inversions must all be written, such as the two branches of sin and cos, the roots of 1 for a whole power, or the cases of a boolean node, the first null ends it, and the parts after it are never computed.
  • AnalyticalEquationSolver.Solve and AnalyticalSetSolver.Solve answer the set-builder on null, at the level where it was met, as they did on the exception. So (x - 1) x! = 0 is still { 1 } together with the x for which x! = 0.
  • The exponential and trigonometric solvers invert their substitutions through InvertEach, which declines on null. Their substitutions are powers of the variable, and those always have a written preimage, so this changes no answer.

Nullable annotations are on and warnings are errors, so the compiler rejects any caller that doesn't handle null.

Measured

  • The unit suite passes on net10.0: 12824 passed, 14 skipped, none failed, of 12838, the same as master. No answer changes, and no test did.
  • The allocation gate passes on all 19 gated benchmarks. SolveHard allocates 0.7% less: it meets inversions with no written preimage, and those allocated an exception each.
  • Time. The first version of this was 3% slower on SolveHard. It computed every part of a composite inversion before looking for a null, where the exception had abandoned the rest at once. Stopping at the first null puts it back: 66.0 ms against 66.9 ms on master, over six alternating runs of each. Solves that meet such an inversion directly, such as x mod 3 = 1, gcd(x, 4) = 2 and erf(x) = 1/2, take 2 to 10% less time.

🤖 Generated with Claude Code

https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura

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.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura
@Rafael-SOWNet
Rafael-SOWNet merged commit 877c449 into master Sep 27, 2026
31 checks passed
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.

1 participant