Repository navigation
Carry the condition an order comparison is decided under (#876), and read a set-builder's predicate inside its binder (#878) - #879
Merged
Conversation
ConditionalSet.InnerSimplify handed the predicate to the generic
argument-expanding helper, which lifts a Providedf out of an argument and puts
it on the whole expression. That is right for a function, whose arguments are
values, and wrong for a node that binds a variable: `{ x : 1/x = 0 }` came back
as `{ } provided not x = 0`, where the x named in the condition is no longer the
x the set ranges over.
Membership is the predicate holding, so the condition did not need hoisting
anywhere. A predicate that is False where its condition holds and undefined
where it does not admits nothing, and the answer is `{ }`. A predicate that is
True under a condition admits exactly the points the condition admits, so the
condition becomes the predicate and stays inside the binder --
`{ x : x/x = 1 }` is `{ x : not x = 0 }`.
The other branch had never worked. For a predicate that holds everywhere the
code returned the node's Codomain, a set-builder's Codomain is Domain.Any, and
SpecialSet.Create has no case for Any -- so `{ x : 1 = 1 }`, `{ x : x = x }` and
`{ x : x/x = 1 }` all threw AngouriBugException out of Simplify on valid input.
No set names every value a symbol could take, which is why Any has none, so a
predicate that holds everywhere is left as written; naming the universal set is
a separate question.
TryContains needed the same reading. At x := a the predicate of
`{ x : x = a and x > a }` is False for real a and undefined for the rest of the
plane, so a is not a member anywhere and membership is decided -- but the code
read only EvaluableBoolean and gave up on a Providedf.
Measured: 6253 tests pass, 0 fail; casbench 117/119 with every verdict and
answer in coverage.md unchanged; propcheck, simpsweep and rootcheck clean.
`{ x : 1/x = 0 }` -> `{ }`, `{ x : x/x = 1 }` -> `{ x : not x = 0 }`,
`{ x : 1 = 1 }` -> `{ x : True }` instead of a crash.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The complex numbers are not ordered, so `i < 0` is NaN and a comparison need not have a truth value. Three rules decided a pair of comparisons as though it always did: x < 0 and x >= 0 -> False at x := i the statement is NaN x < x -> False likewise not (x < 0) or (x < 0) -> True likewise So Simplify did not commute with Substitute, silently, and the default codomain is Domain.Complex. The reductions themselves are right -- they are the answers over the reals -- and what was missing is the condition they hold under. Each now carries it, and Provided drops a condition that is True, so nothing is attached where the operands are already real: `2 < 0 and 2 >= 0` and `abs(x) < abs(x)` still answer False outright, and under MathS.Settings.Codomain = Domain.Real so does `x < 0 and x >= 0`. Nothing outside the limit machinery read that setting before this. The disjunction was the other half of the same law and was missing entirely. The unsatisfiable conjunction was decided while the valid disjunction was not, so the library took the half of excluded middle that needs the operands to be real and skipped the half that needs exactly the same thing. ExhaustiveSigns is the mirror of OppositeSigns: `x < 0 or x >= 0` is True where x is real, and over the reals `piecewise(0 provided x < 0, 0 provided x >= 0)` now reaches 0, which #876 §3 gives as the consequence. DomainCondition is not the mechanism, despite the neighbouring rules using it: it records singularities -- division by zero, log poles -- and says nothing about where an order comparison is defined. Both are needed on `x < x`. The excluded-middle rules keep answering True unconditionally where the proposition has a truth value everywhere, which is why equality, set membership and a boolean variable are unaffected: `(a = b) or not (a = b)` is True with no condition. Only the order comparisons gained one. Measured: 6253 C# tests and 130 F# tests pass, 0 fail; casbench 117/119, 0 wrong, 0 error, 0 timeout, with every verdict and answer in coverage.md unchanged and only timings moved; propcheck 1340 checks, simpsweep 62778 point comparisons over 10463 expressions and rootcheck 596 cases all clean; boolmin unchanged at 6/9. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rafael-SOWNet
force-pushed
the
fix/comparison-truth-condition
branch
from
August 11, 2026 10:28
a9a1e4e to
e70371f
Compare
This was referenced Aug 16, 2026
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 #876 and #878. Based on #877 — that PR is the first commit's parent, so merge it first and this diff shrinks to its own two commits.
What was wrong
The complex numbers are not ordered, so
i < 0isNaN. Three rules decided a pair of order comparisons as though a comparison always had a truth value:x := i, as writtenx < 0 and x >= 0FalseNaNFalse provided x in RRx > 0 and x <= 0FalseNaNFalse provided x in RRx < xFalseNaNFalse provided x in RRx >= xTrueNaNTrue provided x in RRnot (x < 0) or (x < 0)TrueNaNTrue provided x in RRx < 0 or x >= 0NaNTrue provided x in RRx <= 0 or x >= 0NaNTrue provided x in RRSimplifydid not commute withSubstitute, and the default codomain isDomain.Complex. The reductions themselves are right — they are the answers over the reals — so this attaches the condition rather than withdrawing the answer.Entity.Provideddrops a condition that isTrue, so nothing is attached where the condition already holds:That last line is the first time anything outside the limit machinery reads
MathS.Settings.Codomain, which is what #876 asked for.DomainConditionis not the mechanism, despite the neighbouring rules using it. It records singularities — division by zero, log poles — and says nothing about where an order comparison is defined.x < xneeds both.The missing half
OppositeSignsdecided the unsatisfiable conjunction; there was no counterpart for the disjunction, so the library took the half of excluded middle that needs the operands to be real and skipped the half that needs exactly the same thing.ExhaustiveSignsis its mirror, and over the reals it settles #876 §3's stated consequence:Why #877's test rows moved
#877 pinned
not (x < 0) or (x < 0)toTrue, which its own body records as the unsound answer left for later. The six order-comparison rows move to a test that setsCodomain = Domain.Real, whereTrueis honest; the rows that hold unconditionally — equality, set membership, a boolean variable — stay pinned toTrueoutright. The new §2 test asserts a commutation rather than a printed form, so it holds whatever shape the condition takes.#878, which this had to fix to avoid shipping a crash
{ x : x > 0 or x <= 0 }reaches a pre-existing abort once §3's rule fires. Both defects are onmasterwith no help from this branch:The first is a scope error:
ConditionalSetpassed its predicate to the generic argument-expanding helper, which lifts aProvidedfonto the whole expression — right for a function, wrong for a binder, so the bound variable ended up outside its own binder. Reading membership as the predicate holding removes the need to hoist anything: a predicate that isFalsewhere its condition holds and undefined where it does not admits nothing, and one that isTrueunder a condition admits exactly what the condition admits, so the condition becomes the predicate.The crash was a branch that had never worked — for a true predicate the code returned the node's
Codomain, a set-builder'sCodomainisDomain.Any, andSpecialSet.Createhas no case forAny. Nothing names every value a symbol could take, so such a predicate is now left as written.TryContainsneeded the same reading and is fixed with it. Answers gained rather than lost:What this deliberately does not fix
andandorare strict inNaN, not Kleene:(i<0) and FalseisNaNand(i<0) or TrueisNaN. Under that semantics the annihilation and absorption rules are unsound too —True or (True and (x<0))simplifies toTrueand isNaNatx := i— but by a different root cause, and conditioning them one at a time is not the fix. Filed as #880 rather than smuggled in here.{ x : True }is left as written becauseDomain.Anyhas no set. Naming the universal set is its own question.Verification
master; a further 6 rows moved out of Hold excluded middle whichever operand carries the negation (#876) #877's theory into the real-codomain testcasbench117/119, 0 wrong, 0 error, 0 timeout — every verdict and answer incoverage.mdbyte-identical, only timings movedpropcheck1340 checks, 0 failuressimpsweep62778 point comparisons over 10463 expressions, 0 disagreementsrootcheck596 cases, 0 incomplete, 0 unsoundboolminunchanged at 6/9🤖 Generated with Claude Code