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
22 changes: 22 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,28 @@ but 0, and `log`'s own condition already excluded it. A base that is small but n
| `"log(x, 2)".ToEntity().Compile("x").Call(0)` | `(-0, -0)` | `(NaN, 0)` |
| `"log(x, 1)".ToEntity().Simplify()` | `0 provided not x = 1` | `0 provided not x = 0 and not x = 1` |

### A difference of two intervals keeps the parts on either side

**Silent: `A \ B` for two intervals is the part of `A` below `B` and the part above it, each holding the end `B`
leaves.** It was one interval, from an end of `A` to the far end of `B`, where `B` lay inside `A`. Where `B` covered
an end of `A`, the end of the cut was read from `B`'s other end. And an end of `A` that `B` left out was lost.
Membership follows the set, so `5 in ([0; 10] \ (2; 3))` was `False`. Ends such as `sqrt(2)` are compared by their
values, where the difference was left as written. With ends that have no value, `[a; b] \ (a; b)` is left as written,
since it is `{ a, b }` where `a < b` and nothing where `a > b`
([#2290](https://github.com/asc-community/AngouriMath/issues/2290)).

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"[0; 10] \\ (2; 3)".ToEntity().Simplify()` | `[0; 3]` | `[0; 2] \/ [3; 10]` |
| `"[0; 10] \\ [-1; 3)".ToEntity().Simplify()` | `(3; 10]` | `[3; 10]` |
| `"[0; 10] \\ (0; 3)".ToEntity().Simplify()` | `[3; 10]` | `{ 0 } \/ [3; 10]` |
| `"[0; 10] \\ (0; 10)".ToEntity().Simplify()` | `[0; 10]` | `{ 0, 10 }` |
| `"(-oo; +oo) \\ [0; 1)".ToEntity().Simplify()` | `(-oo; 1)` | `(-oo; 0) \/ [1; +oo)` |
| `"[0; sqrt(2)] \\ [1; 2]".ToEntity().Simplify()` | `[0; sqrt(2)] \ [1; 2]` | `[0; 1)` |
| `"(a; b) \\ [a; b]".ToEntity().Simplify()` | `(a; b)` | `{ }` |
| `"[a; b] \\ (a; b)".ToEntity().Simplify()` | `[a; b]` | `[a; b] \ (a; b)` |
| `"5 in ([0; 10] \\ (2; 3))".ToEntity().Simplify()` | `False` | `True` |

### `re` and `im` are the real and the imaginary part

**Not silent: `re(…)` and `im(…)`, and `real(…)` and `imag(…)`, are now the real and the imaginary part, where they were
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,42 +26,67 @@ internal static Set SetSubtractSetAndFiniteSet(Set set, FiniteSet finite)
private static bool Implies(this bool assumption, bool conclusion)
=> !assumption || conclusion;

/// <summary>
/// What is left of <paramref name="A"/> with <paramref name="B"/> taken out: the part of
/// <paramref name="A"/> below <paramref name="B"/> and the part above it, each an
/// interval, a point or nothing.
/// </summary>
/// <remarks>
/// Each part ends where <paramref name="B"/> begins, and holds that end where
/// <paramref name="B"/> does not: <c>[0; 10] \ (2; 3)</c> is <c>[0; 2] \/ [3; 10]</c>,
/// and <c>[0; 10] \ (0; 3)</c> is <c>{ 0 } \/ [3; 10]</c>. One interval from an end of
/// <paramref name="A"/> to the far end of <paramref name="B"/> was answered for the first,
/// <c>[0; 3]</c>, the end of the cut was read from the other end of
/// <paramref name="B"/>, and an end of <paramref name="A"/> that <paramref name="B"/> left
/// was lost. The ends are compared by their values, as the intersection compares them,
/// and kept as written. Where an end has no value the difference is left as it is, unless
/// the ends are the same and <paramref name="B"/> holds every end <paramref name="A"/>
/// holds; then it holds all of <paramref name="A"/>. Where <paramref name="A"/> holds an
/// end that <paramref name="B"/> does not, what is left depends on the order of the ends:
/// <c>[a; b] \ (a; b)</c> is <c>{ a, b }</c> where <c>a &lt; b</c> and nothing where
/// <c>a &gt; b</c>.
/// https://github.com/asc-community/AngouriMath/issues/2290
/// </remarks>
internal static Set SetSubtractIntervalAndInterval(Interval A, Interval B)
{
if (A == B)
return Empty;
if (A.Left == B.Left && A.Right == B.Right)
return new Interval(A.Left, A.LeftClosed && !B.LeftClosed, A.Right, A.RightClosed && !B.RightClosed);
if (A.Left is not Real aLeft ||
A.Right is not Real aRight ||
B.Left is not Real bLeft ||
B.Right is not Real bRight)
if (A.Left == B.Left && A.Right == B.Right && A.LeftClosed.Implies(B.LeftClosed) && A.RightClosed.Implies(B.RightClosed))
return Empty;
if (A.Left.Evaled is not Real { IsNaN: false } aLeft ||
A.Right.Evaled is not Real { IsNaN: false } aRight ||
B.Left.Evaled is not Real { IsNaN: false } bLeft ||
B.Right.Evaled is not Real { IsNaN: false } bRight)
return A.SetSubtract(B);

if (aLeft > aRight)
if (aLeft > aRight || aLeft == aRight && !(A.LeftClosed && A.RightClosed))
return Empty;
if (bLeft > bRight)
return A;
if (aLeft == bRight)
return B.RightClosed ? A.New(A.Left, false, A.Right, A.RightClosed) : A;
if (aRight == bLeft)
return B.LeftClosed ? A.New(A.Left, A.LeftClosed, A.Right, false) : A;
if (aRight < bLeft || aLeft > bRight)
if (bLeft > bRight || bLeft == bRight && !(B.LeftClosed && B.RightClosed))
return A;
if (
(bLeft < aLeft || bLeft == aLeft && A.LeftClosed.Implies(B.LeftClosed))
&&
(bRight > aRight || bRight == aRight && A.RightClosed.Implies(B.RightClosed))
)
return Empty;
if (aLeft >= bLeft && aRight >= bRight)
return new Interval(bRight, aLeft == bLeft ? A.LeftClosed && !B.LeftClosed : !B.LeftClosed,
aRight, aRight == bRight ? A.RightClosed && !B.RightClosed : A.RightClosed);
if (aLeft <= bLeft && aRight <= bRight)
return new Interval(aLeft, aLeft == bLeft ? A.LeftClosed && !B.LeftClosed : A.LeftClosed,
bLeft, aRight == bRight ? A.RightClosed && !B.RightClosed : !B.RightClosed);
return new Interval(aLeft, aLeft == bLeft ? A.LeftClosed && !B.LeftClosed : !B.LeftClosed,
bRight, aRight == bRight ? A.RightClosed && !B.RightClosed : A.RightClosed);
// Below B: from A's left end to whichever comes first, A's right end or B's left end.
var (belowEnd, belowEndValue, belowEndClosed) =
aRight < bLeft ? (A.Right, aRight, A.RightClosed) :
bLeft < aRight ? (B.Left, bLeft, !B.LeftClosed) :
(A.Right, aRight, A.RightClosed && !B.LeftClosed);
// Above B: from whichever comes last, A's left end or B's right end, to A's right end.
var (aboveStart, aboveStartValue, aboveStartClosed) =
aLeft > bRight ? (A.Left, aLeft, A.LeftClosed) :
bRight > aLeft ? (B.Right, bRight, !B.RightClosed) :
(A.Left, aLeft, A.LeftClosed && !B.RightClosed);
var below = Part(A.Left, aLeft, A.LeftClosed, belowEnd, belowEndValue, belowEndClosed);
var above = Part(aboveStart, aboveStartValue, aboveStartClosed, A.Right, aRight, A.RightClosed);
return (below, above) switch
{
(null, null) => Empty,
({ } low, null) => low,
(null, { } high) => high,
(FiniteSet { Count: 1 } low, FiniteSet { Count: 1 } high) => new FiniteSet(low.First(), high.First()),
({ } low, { } high) => low.Unite(high),
};

static Set? Part(Entity left, Real leftValue, bool leftClosed, Entity right, Real rightValue, bool rightClosed)
=> leftValue < rightValue ? new Interval(left, leftClosed, right, rightClosed)
: leftValue == rightValue && leftClosed && rightClosed ? new FiniteSet(left)
: null;
}

internal static Set SetSubtractCSetAndCSet(ConditionalSet intLeft, ConditionalSet intRight)
Expand Down
92 changes: 92 additions & 0 deletions Sources/Tests/UnitTests/Core/Sets/IntervalAndInterval.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
//

using AngouriMath;
using System.Collections.Generic;
using System.Linq;
using Xunit;
using static AngouriMath.Entity;
using static AngouriMath.Entity.Set;
Expand Down Expand Up @@ -86,6 +88,96 @@ public void TwoRaysMeetWhateverTheirEnds(string intersection, string expected)
[Fact] public void Subtraction5() => TestInt(A.SetSubtract(F), Interval(2, true, 3, false));
[Fact] public void Subtraction6() => TestInt(F.SetSubtract(A), Interval(5, false, 6, true));

/// <summary>
/// What is left of an interval with another taken out is the part below it and the part
/// above it, and each part holds the end the second one leaves. One interval from an end
/// of the first to the far end of the second used to be answered, with its ends read from
/// the wrong side. https://github.com/asc-community/AngouriMath/issues/2290
/// </summary>
[Theory]
[InlineData("[0; 10] \\ (2; 3)", "[0; 2] \\/ [3; 10]")]
[InlineData("[0; 10] \\ [2; 3]", "[0; 2) \\/ (3; 10]")]
[InlineData("(0; 10) \\ (2; 3)", "(0; 2] \\/ [3; 10)")]
[InlineData("[0; 10] \\ [-1; 3)", "[3; 10]")]
[InlineData("[0; 10] \\ (-1; 3]", "(3; 10]")]
[InlineData("[0; 10] \\ (5; 12]", "[0; 5]")]
[InlineData("[0; 10] \\ [5; 12)", "[0; 5)")]
[InlineData("[0; 10] \\ (0; 3)", "{ 0 } \\/ [3; 10]")]
[InlineData("[0; 10] \\ (7; 10)", "[0; 7] \\/ { 10 }")]
[InlineData("[0; 10] \\ (0; 10)", "{ 0, 10 }")]
[InlineData("[0; 10] \\ [0; 10)", "{ 10 }")]
[InlineData("[0; 10] \\ [5; 5]", "[0; 10] \\ { 5 }")]
[InlineData("[0; 10] \\ (5; 5)", "[0; 10]")]
[InlineData("[0; sqrt(2)] \\ [1; 2]", "[0; 1)")]
[InlineData("(-oo; +oo) \\ [0; 1)", "(-oo; 0) \\/ [1; +oo)")]
public void ADifferenceKeepsThePartsOnEitherSide(string difference, string expected)
=> Assert.Equal(MathS.FromString(expected).Simplify(), MathS.FromString(difference).Simplify());

/// <summary>
/// The difference holds a point exactly where the first interval does and the second does
/// not, over every order of the four ends, with each end open or closed, and with either
/// interval reversed or at a single point. Two intervals that each have more than one point
/// are subtracted to a set without a difference in it; one that has a point or none is a
/// listed set by then, and the difference may stay.
/// https://github.com/asc-community/AngouriMath/issues/2290
/// </summary>
[Fact]
public void ADifferenceHoldsWhatTheFirstHoldsAndTheSecondDoesNot()
{
var points = Enumerable.Range(-1, 9).Select(k => (Entity)Entity.Number.Rational.Create(k, 2)).ToArray();
var failures = new List<string>();
foreach (var aLeft in Enumerable.Range(0, 4))
foreach (var aRight in Enumerable.Range(0, 4))
foreach (var bLeft in Enumerable.Range(0, 4))
foreach (var bRight in Enumerable.Range(0, 4))
foreach (var flags in Enumerable.Range(0, 16))
{
var a = Interval(aLeft, (flags & 1) != 0, aRight, (flags & 2) != 0);
var b = Interval(bLeft, (flags & 4) != 0, bRight, (flags & 8) != 0);
var difference = a.SetSubtract(b).InnerSimplified;
if (difference is not Set left || left is SetMinusf && aLeft < aRight && bLeft < bRight)
{
failures.Add($"{a} \\ {b} is not decided: {difference}");
continue;
}
foreach (var point in points)
{
var expected = Holds(a, point) && !Holds(b, point);
if (!left.TryContains(point, out var contains) || contains != expected)
failures.Add($"{point} in {a} \\ {b} = {left}: expected {expected}");
}
}
Assert.Empty(failures);

static bool Holds(Interval interval, Entity point)
{
var value = (Entity.Number.Real)point.EvalNumerical();
var (left, right) = ((Entity.Number.Real)interval.Left.EvalNumerical(), (Entity.Number.Real)interval.Right.EvalNumerical());
return (left < value || interval.LeftClosed && left == value) && (value < right || interval.RightClosed && value == right);
}
}

/// <summary>
/// Ends that have no value say whether anything is left only when they are the same and
/// the second interval holds every end the first holds. <c>[a; b] \ (a; b)</c> is
/// <c>{ a, b }</c> where <c>a &lt; b</c> and nothing where <c>a &gt; b</c>, so it stays as
/// it is, and it used to be answered <c>[a; b]</c>.
/// https://github.com/asc-community/AngouriMath/issues/2290
/// </summary>
[Theory]
[InlineData("[a; b] \\ (a; b)")]
[InlineData("[a; b] \\ [a; b)")]
[InlineData("(a; b] \\ (a; b)")]
public void ADifferenceOfSymbolicIntervalsWithAnEndLeftStays(string difference)
=> Assert.IsType<SetMinusf>(MathS.FromString(difference).InnerSimplified);

[Theory]
[InlineData("(a; b) \\ [a; b]")]
[InlineData("[a; b) \\ [a; b]")]
[InlineData("(a; b] \\ (a; b]")]
public void ADifferenceOfSymbolicIntervalsThatCoversIsEmpty(string difference)
=> Assert.Equal(Set.Empty, MathS.FromString(difference).InnerSimplified);

/// <summary>
/// An interval whose ends are reversed holds nothing: it meets nothing, and adds nothing to
/// a union. The meeting used to be the other interval.
Expand Down
Loading