diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 1481f5f6a..634038644 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -628,6 +628,18 @@ conjugate as `0 - 1i`, in decimals, on the one instance that every `i` is, in ev |---|---|---| | `Complex.ImaginaryOne.Conjugate`, after `(1 / Complex.ImaginaryOne).Evaled` with `DowncastingEnabled` off | `0 - 1i` | `-i` | +### `max` and `min` are defined where their arguments are real + +**Silent: `max(x, x)` and `min(x, x)` simplify to `x provided x in RR`, where they were `x`.** The complex numbers are +not ordered, and both nodes are read over the reals, so `max(i, i)` has no value. Their condition said they were +defined everywhere, and `max(x, x)` simplified to `x`, which has a value at `i`. A comparison of two real arguments is +unchanged ([#2256](https://github.com/asc-community/AngouriMath/issues/2256)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"max(x, x)".ToEntity().Simplify()` | `x` | `x provided x in RR` | +| `"max(x, 2)".ToEntity().DomainCondition` | `True` | `x in RR` | + ### `conjugate`, `conj` and `arg` are functions **Not silent: these names followed by a bracket are now the complex conjugate and the principal argument, where they diff --git a/Sources/.editorconfig b/Sources/.editorconfig index 059d1e5ff..5cbc5b29f 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -237,6 +237,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Convenience/BinomialTest.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/Convenience/MaxMinOffTheRealLineTest.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/Convenience/ConjugateAndArgumentTest.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/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs index f8a8bc6fd..10cbe23cf 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs @@ -1327,7 +1327,10 @@ protected override Entity InnerSimplify(bool isExact) public partial record Minf { - private protected override Entity IntrinsicCondition => Boolean.True; + // Defined where both arguments are real: the complex numbers are not ordered, and the node is + // read over the reals, so min(i, i) is NaN. The condition said True, and min(x, x) simplified + // to x, which has a value at x = i. https://github.com/asc-community/AngouriMath/issues/2256 + private protected override Entity IntrinsicCondition => Left.In(MathS.Sets.R) & Right.In(MathS.Sets.R); /// protected override Entity InnerSimplify(bool isExact) @@ -1337,7 +1340,8 @@ protected override Entity InnerSimplify(bool isExact) // Only ordered arguments compare. A complex one is left alone rather // than guessed at, which is what SymPy's Min does too. (Real l, Real r) => l.EDecimal.CompareTo(r.EDecimal) <= 0 ? l : r, - var (l, r) when l == r => l, + // min(a, a) is a where a is real, and has no value where it is not. + var (l, r) when l == r => l.Provided(l.In(MathS.Sets.R)).InnerSimplified(isExact), _ => null }, (@this, a, b) => ((Minf)@this).New(a, b), isExact); @@ -1345,7 +1349,8 @@ protected override Entity InnerSimplify(bool isExact) public partial record Maxf { - private protected override Entity IntrinsicCondition => Boolean.True; + // As Minf's: defined where both arguments are real. https://github.com/asc-community/AngouriMath/issues/2256 + private protected override Entity IntrinsicCondition => Left.In(MathS.Sets.R) & Right.In(MathS.Sets.R); /// protected override Entity InnerSimplify(bool isExact) @@ -1353,7 +1358,7 @@ protected override Entity InnerSimplify(bool isExact) (a, b) => (a, b) switch { (Real l, Real r) => l.EDecimal.CompareTo(r.EDecimal) >= 0 ? l : r, - var (l, r) when l == r => l, + var (l, r) when l == r => l.Provided(l.In(MathS.Sets.R)).InnerSimplified(isExact), _ => null }, (@this, a, b) => ((Maxf)@this).New(a, b), isExact); diff --git a/Sources/Tests/UnitTests/Convenience/MaxMinOffTheRealLineTest.cs b/Sources/Tests/UnitTests/Convenience/MaxMinOffTheRealLineTest.cs new file mode 100644 index 000000000..7e69ac762 --- /dev/null +++ b/Sources/Tests/UnitTests/Convenience/MaxMinOffTheRealLineTest.cs @@ -0,0 +1,56 @@ +// +// 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; + +namespace AngouriMath.Tests.Convenience +{ + /// + /// max and min are defined where both arguments are real, as their values are: the complex numbers are not + /// ordered, and max(i, i) is NaN. Their condition said True, so max(x, x) simplified to + /// x, which has a value at x = i. + /// #2256 + /// + [Trait("Area", "Convenience")] + public sealed class MaxMinOffTheRealLineTest + { + [Theory] + [InlineData("max(x, 2)", "i", false)] + [InlineData("min(x, 2)", "1/2 + 6/5 * i", false)] + [InlineData("max(x, 2)", "-3", true)] + [InlineData("min(x, x)", "pi", true)] + public void TheConditionIsWhereTheValueIs(string expression, string at, bool defined) + { + var condition = expression.ToEntity().DomainCondition.Substitute("x", at.ToEntity()).Evaled; + Assert.Equal(defined ? Entity.Boolean.True : Entity.Boolean.False, condition); + // Where it is not defined, evaluation gives NaN or leaves the node, as max(i, 2) is left: no number. + var value = expression.ToEntity().Substitute("x", at.ToEntity()).Evaled; + Assert.Equal(defined, value is Entity.Number && !value.IsNaN); + } + + [Theory] + [InlineData("max(i, i)")] + [InlineData("min(1 + i, 1 + i)")] + public void OfAComplexNumberWithItselfItHasNoValue(string expression) + { + Assert.True(expression.ToEntity().Evaled.IsNaN, $"{expression} evaluates to {expression.ToEntity().Evaled}"); + Assert.True(expression.ToEntity().Simplify().IsNaN, $"{expression} simplifies to {expression.ToEntity().Simplify()}"); + } + + [Theory] + [InlineData("max(x, x)")] + [InlineData("min(x, x)")] + public void ASimplificationHasNoValueWhereTheOriginalHasNone(string expression) + { + var simplified = expression.ToEntity().Simplify(); + Assert.True(simplified.Substitute("x", "i".ToEntity()).Evaled.IsNaN, $"{simplified} at i"); + Assert.Equal(Entity.Number.Integer.Create(3), simplified.Substitute("x", 3).Evaled); + } + } +} diff --git a/Sources/Tests/UnitTests/Convenience/RoundMinMaxGcdTest.cs b/Sources/Tests/UnitTests/Convenience/RoundMinMaxGcdTest.cs index f52e6f0d7..10d7eb41e 100644 --- a/Sources/Tests/UnitTests/Convenience/RoundMinMaxGcdTest.cs +++ b/Sources/Tests/UnitTests/Convenience/RoundMinMaxGcdTest.cs @@ -68,9 +68,12 @@ public void RoundIsNotFloorOfTheArgumentPlusAHalf(string at) public void MinAndMaxCompareWhereTheArgumentsAreOrdered(string input, int expected) => Assert.Equal(Entity.Number.Integer.Create(expected), input.ToEntity().Simplify()); + // That thing where it is real: the complex numbers are not ordered, and min(i, i) has no value. + // https://github.com/asc-community/AngouriMath/issues/2256 [Theory] - [InlineData("min(x, x)", "x")] - [InlineData("max(x, x)", "x")] + [InlineData("min(x, x)", "x provided x in RR")] + [InlineData("max(x, x)", "x provided x in RR")] + [InlineData("min(3, 3)", "3")] public void MinAndMaxOfOneThingWithItselfIsThatThing(string input, string expected) => Assert.Equal(expected.ToEntity(), input.ToEntity().Simplify());