Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,25 @@ reached 3^12 cases and 8 GB inside integration by parts
| `(x < 0 and x = 0).Simplify()` | `False provided x in RR` | `False` — the condition was over-strong, one conjunct is false wherever `x` is |
| `(x > 0 and x > 0).Evaled` | `x > 0` | `x > 0` (unchanged) |

### A union with an empty interval is the other set

**Wrong answer fixed.** Two intervals were joined wherever one ended where the other began, before
either was asked whether it was empty. `[1; 0]` is empty, and it ends at 0 where `[0; 1]` begins,
so `[0; 1] \/ [1; 0]` was joined into `{ 1 }`, and `[3; 1] \/ [1; 2]` into `[3; 2]`, which is
empty. An empty interval now adds nothing. With ends that are not numbers either interval may be
empty -- `[a; b]` is, when `a` is above `b` -- so two such intervals are no longer joined unless an
end is infinite, and `[a; b] \/ [b; c]`, which was `[a; c]`, is left as written.
[#1634](https://github.com/asc-community/AngouriMath/issues/1634). Both columns measured on a
build, `v2.5.0` against this change.

| `"….".ToEntity().Simplify()` of | Was (2.5.0) | Is |
|---|---|---|
| `[0; 1] \/ [1; 0]` | `{ 1 }` — wrong | `[0; 1]` |
| `[3; 1] \/ [1; 2]` | `[3; 2]`, which is empty — wrong | `[1; 2]` |
| `[a; b] \/ [b; a]` | `{ b }` — wrong | `[a; b] \/ [b; a]` |
| `[a; b] \/ [b; c]` | `[a; c]` — wrong where `a > b` or `b > c` | `[a; b] \/ [b; c]` |
| `(-oo; x] \/ [x; +oo)` | `RR` | `RR` (unchanged: neither can be empty) |

### `ZZ*` and `ZZ+` are the non-negative and the positive integers

`ZZ*` = `{0, 1, 2, ...}` and `ZZ+` = `{1, 2, 3, ...}` are special sets, spelled as MathWorld spells
Expand Down
50 changes: 41 additions & 9 deletions Sources/AngouriMath/Core/Entity/Omni/Sets/SetOperators.Union.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,28 @@ internal static Set UniteFiniteSetAndSet(FiniteSet finite, Set set)
return sb.IsEmpty ? set : sb.ToFiniteSet().Unite(set);
}

// TODO: it requires cleaning
/// <summary>
/// The union of two intervals: one interval where they overlap or touch, and as written
/// where they do not, or where that cannot be told.
/// </summary>
/// <remarks>
/// An empty interval adds nothing -- one whose left end is above its right, or at it with
/// an end open -- so <c>[0; 1] \/ [1; 0]</c> is <c>[0; 1]</c>. Two intervals that touch are
/// one only when neither is empty. Touching was tested first, so <c>[1; 0]</c>, touching
/// <c>[0; 1]</c> at 0, was joined to it into <c>{ 1 }</c>, and <c>[a; b] \/ [b; a]</c> into
/// <c>{ b }</c>. Ends that are not numbers do not say whether an interval is empty, unless
/// the ends are one expression closed at both or one end is infinite, as in
/// <c>(-oo; x] \/ [x; +oo)</c>; otherwise the union is left as written.
/// https://github.com/asc-community/AngouriMath/issues/1634
/// </remarks>
internal static Set UniteIntervalAndInterval(Interval A, Interval B)
{
if (IsEmpty(A))
return B;
if (IsEmpty(B))
return A;
if (!IsNotEmpty(A) || !IsNotEmpty(B))
return A.Unite(B);
if (A.Left == B.Right && (A.LeftClosed || B.RightClosed))
return new Interval(B.Left, B.LeftClosed, A.Right, A.RightClosed);
if (A.Right == B.Left && (A.RightClosed || B.LeftClosed))
Expand All @@ -120,14 +139,11 @@ A.Right is not Real aRight ||
B.Left is not Real bLeft ||
B.Right is not Real bRight)
return A.Unite(B);
if (aLeft == aRight && A.LeftClosed && A.RightClosed)
UniteFiniteSetAndSet(new FiniteSet(aLeft), B);
if (bLeft == bRight && B.LeftClosed && B.RightClosed)
UniteFiniteSetAndSet(new FiniteSet(bLeft), A);
if (aLeft >= aRight)
return B;
if (bLeft >= bRight)
return A;
// Neither is empty, so ends that are equal are a point closed at both.
if (aLeft == aRight)
return UniteFiniteSetAndSet(new FiniteSet(aLeft), B);
if (bLeft == bRight)
return UniteFiniteSetAndSet(new FiniteSet(bLeft), A);
if (aLeft == bRight && !A.LeftClosed && !B.RightClosed)
return A.Unite(B);
if (bLeft == aRight && !B.LeftClosed && !A.RightClosed)
Expand All @@ -151,6 +167,22 @@ B.Left is not Real bLeft ||
return new Interval(left, leftClosed, right, rightClosed);
}

/// <summary>Known to be empty: numeric ends out of order, or at one point with an end open.</summary>
private static bool IsEmpty(Interval interval)
=> interval.Left is Real left && interval.Right is Real right
&& (left > right || left == right && !(interval.LeftClosed && interval.RightClosed));

/// <summary>
/// Known not to be empty: numeric ends in order, the same end at both sides and closed, or
/// an end at an infinity, beyond any end the other side can have.
/// </summary>
private static bool IsNotEmpty(Interval interval)
=> interval.Left is Real left && interval.Right is Real right
? left < right || left == right && interval.LeftClosed && interval.RightClosed
: interval.Left == interval.Right
? interval.LeftClosed && interval.RightClosed
: interval.Left == Real.NegativeInfinity || interval.Right == Real.PositiveInfinity;

internal static Set UniteCSetAndCSet(ConditionalSet intLeft, ConditionalSet intRight)
{
(intLeft, intRight) = MergeToOneVariable(intLeft, intRight);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.Extensions;
using Xunit;
using static AngouriMath.Entity;

namespace AngouriMath.Tests.Core.Sets
{
/// <summary>
/// An interval whose left end is above its right is empty, and a union with it is the other
/// set. Two intervals were joined wherever they touched, before either was asked whether it was
/// empty: <c>[0; 1] \/ [1; 0]</c> became <c>{ 1 }</c> and <c>[a; b] \/ [b; a]</c> became <c>{ b }</c>.
/// https://github.com/asc-community/AngouriMath/issues/1634
/// </summary>
[Trait("Area", "Core")]
public sealed class AnEmptyIntervalAddsNothingTest
{
private static Entity IsIn(string value, string set) => $"{value} in ({set})".ToEntity().Simplify();

/// <summary>The union is decided at points on both sides of every end, not read off its printed shape.</summary>
[Theory]
[InlineData("[0; 1] \\/ [1; 0]", "0, 1/2, 1", "-1, 3/2")]
[InlineData("[1; 0] \\/ [0; 1]", "0, 1/2, 1", "-1, 3/2")]
[InlineData("[3; 1] \\/ [1; 2]", "1, 3/2, 2", "0, 5/2, 3")]
[InlineData("[0; 1] \\/ [1; 1)", "0, 1/2, 1", "-1, 3/2")]
[InlineData("[2; 2] \\/ [0; 1]", "0, 1, 2", "-1, 3/2, 3")]
[InlineData("[0; 1] \\/ [1; 2]", "0, 1, 2", "-1, 3")]
public void TheUnionHasTheMembersOfItsNonEmptyParts(string union, string members, string others)
{
var simplified = union.ToEntity().Simplify().Stringize();
foreach (var member in members.Split(", "))
Assert.Equal(Boolean.True, IsIn(member, simplified));
foreach (var other in others.Split(", "))
Assert.Equal(Boolean.False, IsIn(other, simplified));
}

/// <summary>
/// With ends that are not numbers, either interval may be empty, so touching is not joining:
/// the union is left as written. An end at an infinity leaves nothing to doubt.
/// </summary>
[Fact]
public void EndsThatAreNotNumbersAreNotJoinedWhereEitherMayBeEmpty()
{
Assert.IsType<Set.Unionf>("[a; b] \\/ [b; a]".ToEntity().Simplify());
Assert.IsType<Set.Unionf>("[a; b] \\/ [b; c]".ToEntity().Simplify());
Assert.Equal("RR", "(-oo; x] \\/ [x; +oo)".ToEntity().Simplify().Stringize());
}
}
}
Loading