Skip to content

A set-builder's predicate is expanded like a function argument, so its condition escapes the binder — and a predicate that holds everywhere throws #878

Closed

Description

@Rafael-SOWNet

Found while fixing §2/§3 of #876, whose fix reaches the crash below from a new spelling. Both defects are on master at 363edfa3 with no help from that work. .NET 10, default settings.

1. A predicate's condition escapes its binder

{ x : 1/x = 0 }.Simplify()   ->   {  } provided not x = 0

The x named in the condition is not the x the set ranges over — it is bound by the set-builder, and the condition has been lifted outside it. As an expression this is a scope error: whatever x means outside the set, it is not the one being quantified.

The cause is that ConditionalSet.InnerSimplify delegates to ExpandOnTwoAndTArguments with the predicate as an ordinary argument (Evaluation.Omni.Classes.cs:49). That helper lifts a Providedf out of an argument and puts it on the whole expression (Evaluation.Classes.cs:159-160), which is right for a function — arguments are values — and wrong for a node that binds a variable. The same applies to its Piecewise, Matrix and FiniteSet expansion.

The answer is also weaker than it needs to be. Membership is the predicate holding, so a predicate that is False where its condition holds and undefined where it does not admits nothing at all, and the set is {} with no condition to place anywhere.

2. A predicate that holds everywhere throws

{ x : 1 = 1 }.Simplify()      ->   AngouriBugException: The given domain is not presented in those possible
{ x : x = x }.Simplify()      ->   the same
{ x : x / x = 1 }.Simplify()  ->   the same

For a true predicate InnerSimplify returns the node's Codomain, ConditionalSet.Codomain is Domain.Any (Domains.Classes.cs:326), and SpecialSet.Create has no case for Any — it throws (Entity.Omni.Classes.cs:524). So that branch has never worked for a set-builder as parsed: every one whose predicate reduces to True throws out of Simplify instead of answering.

AngouriBugException asks the caller to report a bug, so this is not a wrong answer a user can work around — it is an abort on valid input.

There is no set that names every value a symbol could take, which is why Domain.Any has no SpecialSet. Leaving the set as written is available and honest; naming the universal set is a separate question.

3. TryContains declines what it can decide

("{ x : x = a and x > a }").TryContains("a", out _)   ->   false, once the predicate carries a condition

Same reasoning as §1 in a second place (Entity.Omni.Classes.cs:440): the predicate at 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 reads only EvaluableBoolean and gives up on a Providedf.

This one is currently latent: it takes a predicate whose reduction carries a condition to reach it, which #876's fix makes common.

What I am not claiming

That Providedf-hoisting is wrong for every binder in the library — I checked ConditionalSet and did not audit Sumf, Productf, Integralf or Derivativef for the same shape. §1's cause is read off Evaluation.Classes.cs:159-160 and confirmed by the printed result, not by instrumenting the pass.

Activity

  1. Happypig375 commented on Aug 11, 2026

    @Happypig375
    Member

    It might be reasonable to define a universal set that contains all complex, boolean, tensors etc. The problem is how the solution to x - x = 0 can include complexes and tensors but not booleans.

  2. Rafael-SOWNet commented on Aug 16, 2026

    @Rafael-SOWNet
    MemberAuthor

    Re-measured on master at 2d128f3c. Both defects are fixed, by #879 (35f4ae5a, "…and read a set-builder's predicate inside its binder (#878)").

    input then now
    { x : 1/x = 0 } { } provided not x = 0 { }
    { x : 1 = 1 } AngouriBugException { x : True }
    { x : x = x } AngouriBugException { x : True }
    { x : x / x = 1 } AngouriBugException { x : not x = 0 }

    §1 is fixed in the stronger of the two ways this issue offered: the condition does not escape the binder and the empty answer carries no condition at all, since a predicate that is false where its condition holds admits nothing. §2 no longer throws, and { x : x / x = 1 } keeps its condition inside the binder, which is the part that shows the fix is about scope rather than about suppressing the exception.

    @Happypig375's question is not resolved by that, and I do not think it is this issue's.

    It might be reasonable to define a universal set that contains all complex, boolean, tensors etc. The problem is how the solution to x - x = 0 can include complexes and tensors but not booleans.

    { x : 1 = 1 } currently answers { x : True } — a set-builder whose predicate is True — rather than naming a universal set. That is a well-formed answer and no longer a crash, so nothing is broken by leaving it; but it is also not the universal set you were asking about, and the tension you named is real: a universal set that contains booleans makes x - x = 0 answer wrongly, and one that does not is not universal.

    That reads to me like its own proposal rather than a leftover of this bug. Happy to file it with your framing if you want it tracked separately; otherwise closing this seems right, since both measured defects are gone.

  3. Happypig375 commented on Aug 16, 2026

    @Happypig375
    Member

    open the issue

  4. Rafael-SOWNet commented on Aug 21, 2026

    @Rafael-SOWNet
    MemberAuthor

    @Happypig375 — opened as #996, with your framing quoted and the state of Domain.Any measured as it stands today.

    Re-measured all three sections on master at 5211ccd6, which is nine commits past the check above. §3 was the one my earlier comment did not cover; it is fixed too.

    § input then now
    1 { x : 1/x = 0 } { } provided not x = 0 { }
    2 { x : 1 = 1 } AngouriBugException { x : True }
    2 { x : x = x } AngouriBugException { x : True }
    2 { x : x / x = 1 } AngouriBugException { x : not x = 0 }
    3 a in { x : x = a and x > a } undecided decided: no

    §3's fix is in ConditionalSet.TryContains: a predicate that is False where its condition holds and has no truth value where it does not is not membership either way, so membership is decided even though the condition is not. The control cases still behave — 3 in { x : x > 2 } decides yes, 1 in { x : x > 2 } decides no, and y in { x : x > 2 } stays undecided rather than being answered.

    Nothing measured in this issue is still open, and the design question it raised is now tracked in #996, so this can close.

    One thing found while re-measuring and filed separately as #995: SpecialSet.TryContains answers with MayContain's deliberately permissive result, so [1, 2] in BB, [1, 2] in ZZ and [1, 2] in RR are all True — a matrix is a member of every special set at once, ZZ /\ BB included. Different node, different cause, so it is not part of this one.

  5. Rafael-SOWNet commented on Aug 27, 2026

    @Rafael-SOWNet
    MemberAuthor

    Re-measured on master at a2edfc33 during a pre-release sweep. Both defects are gone, and §1
    came out as the answer this issue argued for rather than merely stopping the leak.

    1. The predicate's condition no longer escapes its binder

    { x : 1/x = 0 }.Simplify()   ->   {  }
    

    Not { } provided not x = 0. This issue makes the case that the stronger answer is the right
    one — "membership is the predicate holding, so a predicate that is False where its condition
    holds and undefined where it does not admits nothing at all, and the set is {} with no
    condition to place anywhere"
    — and that is what it gives.

    2. A predicate that holds everywhere no longer throws

    now
    { x : 1 = 1 }.Simplify() { x : True }
    { x : x = x }.Simplify() { x : True }
    { x : x / x = 1 }.Simplify() { x : not x = 0 }

    No AngouriBugException. The third keeps the condition inside the binder, where it belongs.

    Worth noting the class rather than just the instance: "AngouriBugException reachable from
    ordinary input"
    was also live in five other public entry points until today —
    SumAll, MultiplyAll, Sumf.Sum, Mulf.Multiply and MathS.Vector on an empty
    sequence (#1028, #1096). An exception whose message asks the caller to report a bug against this
    repository is worth grepping for whenever one turns up.

    Closing is yours — flagging it because a release is being prepared and this reads as an open
    crash report while it is not one any more.

  6. Rafael-SOWNet commented on Aug 31, 2026

    @Rafael-SOWNet
    MemberAuthor

    Measured on master at 9d9b21e4, .NET 10, default settings. All three sections, on the issue's own inputs.

    §1 — a predicate's condition escapes its binder. Fixed, and with the answer §1 argued for rather than a relocated condition:

    { x : 1/x = 0 }.Simplify()   ->   {  }
    

    Membership is the predicate holding, and a predicate that is False where its condition holds admits nothing — so the set is empty and there is no condition left to place anywhere.

    §2 — a predicate that holds everywhere throws. Fixed; no AngouriBugException:

    { x : 1 = 1 }.Simplify()      ->   { x : True }
    { x : x = x }.Simplify()      ->   { x : True }
    { x : x / x = 1 }.Simplify()  ->   { x : not x = 0 }
    

    And it takes the option the issue named as available and honest — the set is left as written, rather than a universal SpecialSet being invented for it. Whether there is a universal set stays #996's question, unprejudiced. { x : x / x = 1 } is the nicest of the three: it keeps the one real exclusion instead of collapsing to True.

    §3 — TryContains declines what it can decide. Fixed:

    { x : x = a and x > a }.TryContains(a)  ->  decided, and not a member
    { x : x = 3 }.TryContains(3)            ->  decided, a member
    { x : x = 3 }.TryContains(4)            ->  decided, not a member
    

    The predicate at x := a reduces to False provided a in RR — False where its condition holds and undefined elsewhere — so membership is decided, and it is now read that way rather than given up on for not being an EvaluableBoolean.

    Closing as fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions