From 1d65cf9b968dc9e1becf6e1a6062e89d279021c5 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 30 Oct 2026 03:26:15 +0000 Subject: [PATCH] Membership in a set operation is decided by Kleene's connectives Unionf.TryContains returned the right side's verdict whenever the left did not say yes, so a left side that left membership open and a right side that excluded the element made the union False. 2^89 - 1 is a Mersenne prime, past the machine word, where PP leaves membership open: (2^89 - 1) in (PP \/ [0; 1]) False, on 2.6.0 and master (2^89 - 1) in ([0; 1] \/ PP) left as written A union contains what either side contains. It is now Kleene's or: a member where either side decides it is, outside where both decide it is not, and open otherwise. Intersectionf and SetMinusf were not wrong. Each decided only where both sides did. They are now Kleene's and: an element one side decides is outside, or that a difference's right side decides is inside, is outside whatever the other side says. So (2^89 - 1) in ([0; 1] \ PP) and (2^89 - 1) in (PP \ [0; 10^30]) are False, where they were left open. Contains.cs pinned 1 in {x} \ {1} as undecided. 1 is in {1}, so it is outside the difference whatever x is, and the row now expects it decided False. Every other row of that table is unchanged. MembershipInASetOperationTest: 17 cases, of which 5 fail on master, the three union answers and the two differences. Fixes #2219. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 15 ++++++ Sources/.editorconfig | 3 ++ .../Core/Entity/Omni/Entity.Omni.Classes.cs | 50 ++++++++++++----- Sources/Tests/UnitTests/Core/Sets/Contains.cs | 4 +- .../Sets/MembershipInASetOperationTest.cs | 54 +++++++++++++++++++ 5 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 Sources/Tests/UnitTests/Core/Sets/MembershipInASetOperationTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 9aa7e4a08..6d2609c2a 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -521,6 +521,21 @@ around a principal `n`-th root of an `n`-th power, for a whole `n >= 2`, establi |---|---|---| | `"forall t in RR : t >= 0 implies sqrt(t^2) = t".ToEntity().Simplify()` | `forall t in RR : t >= 0 implies sqrt(t ^ 2) = t` | `True` | +### Membership in a union is not decided by one side alone + +**Silent: a membership answered `False` is left open, where it was wrong; and some left open are decided.** Membership +in a union, an intersection and a set difference is decided from each side's by Kleene's connectives. A union was +`False` where one side left membership open and the other excluded the element: `2^89 - 1` is a Mersenne prime past +the machine word, `PP` leaves its membership open, and it was outside `PP \/ [0; 1]`. An intersection and a difference +were decided only where both sides were, and are now decided where one side settles it +([#2219](https://github.com/asc-community/AngouriMath/issues/2219)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"(2^89 - 1) in (PP \/ [0; 1])".ToEntity().Evaled` | `False` | `618970019642690137449562111 in PP \/ [0; 1]` | +| `"(2^89 - 1) in ([0; 1] \ PP)".ToEntity().Evaled` | `618970019642690137449562111 in [0; 1] \ PP` | `False` | +| `"(2^89 - 1) in (PP \ [0; 10^30])".ToEntity().Evaled` | `618970019642690137449562111 in PP \ [0; 1000000000000000000000000000000]` | `False` | + ### A system is not answered with a copy of one of its rows **Silent: a repeated row goes.** Two branches of an elimination can reach one solution, and each was answered: two diff --git a/Sources/.editorconfig b/Sources/.editorconfig index c04f68364..988cc116c 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -466,6 +466,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Core/Sets/PrimeSetTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[Tests/UnitTests/Core/Sets/MembershipInASetOperationTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [Tests/UnitTests/Calculus/InverseHyperbolicSecantIntegralTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs index e4c83ff6a..d5b79ca72 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs @@ -827,9 +827,23 @@ public override bool TryContains(Entity entity, out bool contains) { contains = false; if (Left is not Set left || Right is not Set right) return false; - if (left.TryContains(entity, out contains) && contains) return true; - if (right.TryContains(entity, out contains)) return true; - return false; + // Kleene's or: a member of either side is a member, and an element is outside only + // where both sides decide it is. One side undecided and the other excluding it leaves + // it undecided: 2^89 - 1 in PP \/ [0; 1] is not False, the number being prime. + // https://github.com/asc-community/AngouriMath/issues/2219 + var leftDecided = left.TryContains(entity, out var inLeft); + if (leftDecided && inLeft) + { + contains = true; + return true; + } + var rightDecided = right.TryContains(entity, out var inRight); + if (rightDecided && inRight) + { + contains = true; + return true; + } + return leftDecided && rightDecided; } internal override Priority Priority => Priority.Union; @@ -876,12 +890,17 @@ public override bool TryContains(Entity entity, out bool contains) contains = false; if (Left is not Set left || Right is not Set right) return false; - if (left.TryContains(entity, out var leftContains) && right.TryContains(entity, out var rightContains)) - { - contains = leftContains && rightContains; + // Kleene's and: an element either side decides is outside is outside, whatever the + // other says, and a member only where both decide it is. + // https://github.com/asc-community/AngouriMath/issues/2219 + var leftDecided = left.TryContains(entity, out var inLeft); + if (leftDecided && !inLeft) return true; - } - return false; + var rightDecided = right.TryContains(entity, out var inRight); + if (rightDecided && !inRight) + return true; + contains = leftDecided && rightDecided; + return contains; } internal override Priority Priority => Priority.Intersection; @@ -930,12 +949,17 @@ public override bool TryContains(Entity entity, out bool contains) contains = false; if (Left is not Set left || Right is not Set right) return false; - if (left.TryContains(entity, out var leftContains) && right.TryContains(entity, out var rightContains)) - { - contains = leftContains && !rightContains; + // In the left and not in the right, by Kleene's and: outside where the left decides it + // is not a member or the right decides it is, whatever the other says. + // https://github.com/asc-community/AngouriMath/issues/2219 + var leftDecided = left.TryContains(entity, out var inLeft); + if (leftDecided && !inLeft) return true; - } - return false; + var rightDecided = right.TryContains(entity, out var inRight); + if (rightDecided && inRight) + return true; + contains = leftDecided && rightDecided; + return contains; } internal override Priority Priority => Priority.SetMinus; diff --git a/Sources/Tests/UnitTests/Core/Sets/Contains.cs b/Sources/Tests/UnitTests/Core/Sets/Contains.cs index f100a62c6..74ebe0603 100644 --- a/Sources/Tests/UnitTests/Core/Sets/Contains.cs +++ b/Sources/Tests/UnitTests/Core/Sets/Contains.cs @@ -86,7 +86,9 @@ public sealed class Contains [InlineData("{x} \\ {x}", "x", false)] [InlineData("{x} \\ {y}", "1", false, false)] [InlineData("{x} \\ {y}", "z", false, false)] - [InlineData("{x} \\ {1}", "1", false, false)] + // 1 is in {1}, and so outside the difference whatever x is: decided, by Kleene's and. + // https://github.com/asc-community/AngouriMath/issues/2219 + [InlineData("{x} \\ {1}", "1", false)] [InlineData("{x} \\ {1}", "x", false, false)] // A closed non-leaf is not a number and not a boolean, so it is a member of diff --git a/Sources/Tests/UnitTests/Core/Sets/MembershipInASetOperationTest.cs b/Sources/Tests/UnitTests/Core/Sets/MembershipInASetOperationTest.cs new file mode 100644 index 000000000..bba9830ab --- /dev/null +++ b/Sources/Tests/UnitTests/Core/Sets/MembershipInASetOperationTest.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using AngouriMath; +using AngouriMath.Extensions; +using Xunit; +using static AngouriMath.Entity; + +namespace AngouriMath.Tests.Core.Sets +{ + /// + /// Membership in a union, an intersection and a difference of sets is decided from each side's by Kleene's + /// connectives, where a side may leave it open: 2^89 - 1, a Mersenne prime, is past the machine word + /// and its membership in PP is left open. A union of PP with a set excluding it was + /// False. #2219 + /// + [Trait("Area", "Core")] + public sealed class MembershipInASetOperationTest + { + [Theory] + // One side open, the other excluding it: open, in either order. + [InlineData("(2^89 - 1) in (PP \\/ [0; 1])")] + [InlineData("(2^89 - 1) in ([0; 1] \\/ PP)")] + [InlineData("(2^89 - 1) in (PP \\/ (-oo; 0))")] + [InlineData("(2^89 - 1) in (PP \\/ ({2} \\/ [5; 7]))")] + // One side open, the other containing it: open for an intersection, and for a difference whose right side is open. + [InlineData("(2^89 - 1) in (PP /\\ [0; 10^30])")] + [InlineData("(2^89 - 1) in ([0; 10^30] \\ PP)")] + [InlineData("(2^89 - 1) in (PP \\ [0; 1])")] + public void LeftOpen(string input) + => Assert.IsType(input.ToEntity().Evaled); + + [Theory] + // A union holds where either side does. + [InlineData("(2^89 - 1) in (PP \\/ [0; 10^30])", "True")] + [InlineData("2 in (PP \\/ [5; 7])", "True")] + [InlineData("6 in (PP \\/ [5; 7])", "True")] + [InlineData("4 in (PP \\/ [5; 7])", "False")] + // An intersection fails where either side does. + [InlineData("(2^89 - 1) in (PP /\\ (-oo; 0))", "False")] + [InlineData("(2^89 - 1) in (PP /\\ [0; 1])", "False")] + [InlineData("7 in (PP /\\ [5; 10^30])", "True")] + // A difference fails where its left side does, or its right side holds. + [InlineData("(2^89 - 1) in ([0; 1] \\ PP)", "False")] + [InlineData("(2^89 - 1) in (PP \\ [0; 10^30])", "False")] + [InlineData("7 in (PP \\ [0; 5])", "True")] + public void Decided(string input, string expected) + => Assert.Equal(expected.ToEntity(), input.ToEntity().Evaled); + } +}