diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index c8c0ada32..bff9562b8 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -683,6 +683,17 @@ real roots, so those are now the kinks, and each piece is solved exactly | `"x^2/abs(1-x^2) = x".Solve("x")` | `{ 0, 0.618…, 1.618…, 1.618… + 1.7E-9i, … }` | `{ 0, (-1 + sqrt(5)) / 2, (1 + sqrt(5)) / 2 }` | | `"abs(x^2 - 4) = 3*x".Solve("x")` | `{ 1, 4, 4.0000000000000062… + 1.6E-7i }` | `{ 1, 4 }` | +### A piecewise's condition under a reading asks only the case it takes + +**Silent: `DomainConditionIn` of a `piecewise` is where the case it takes is defined, as `DomainCondition` already +reads it (#1817), where it asked every case to be defined.** `piecewise(sqrt(x) provided x > 0, 1)` is 1 at `-4`, and its +condition over the reals failed there. The library asks for this condition under the ambient reading, so the change +reaches whatever consults it there ([#2256](https://github.com/asc-community/AngouriMath/issues/2256)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"piecewise(sqrt(x) provided x > 0, 1)".ToEntity().DomainConditionIn(Domain.Real)` | `x >= 0 and x in RR` | `x > 0 and x >= 0 or not x > 0 or not x in RR` | + ### A system with more or fewer equations than unknowns is solved or declined **Not silent: what was an exception is an answer or `null`.** `EquationSystem.Solve` threw diff --git a/Sources/.editorconfig b/Sources/.editorconfig index 58d76249a..4e52389a2 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -252,6 +252,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Convenience/IversonTest.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/PiecewiseConditionInAReadingTest.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/MatrixFunctionsInAnExpressionTest.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.Definition.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Definition.cs index 8953f763a..006c9f9d8 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Definition.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Definition.cs @@ -66,6 +66,13 @@ partial record Entity /// https://github.com/asc-community/AngouriMath/issues/1817 /// private static Entity WhereTheCaseItTakesIsDefined(Piecewise piecewise) + => WhereTheCaseItTakesIsDefined(piecewise, static entity => entity.DomainCondition); + + /// + /// As above, with for where a predicate and a case's expression are defined: + /// , or under a reading. + /// + private static Entity WhereTheCaseItTakesIsDefined(Piecewise piecewise, System.Func conditionOf) { Entity defined = Boolean.False; Entity noEarlierCase = Boolean.True; @@ -74,8 +81,8 @@ private static Entity WhereTheCaseItTakesIsDefined(Piecewise piecewise) // Never taken, and passed over by the cases after it. if (@case.Predicate.Evaled is var decided && (decided.IsNaN || decided == Boolean.False)) continue; - var predicateDomain = @case.Predicate.DomainCondition; - defined |= @case.Predicate & @case.Expression.DomainCondition & noEarlierCase; + var predicateDomain = conditionOf(@case.Predicate); + defined |= @case.Predicate & conditionOf(@case.Expression) & noEarlierCase; noEarlierCase &= predicateDomain == Boolean.True ? !@case.Predicate : !@case.Predicate | !predicateDomain; } return defined; @@ -371,6 +378,11 @@ private static IEnumerable Conjuncts(Entity condition) /// public Entity DomainConditionIn(Domain reading) { + // A piecewise is defined where the case it takes is, as DomainCondition reads it, and not where every case + // is: tan(piecewise(pi / x provided x > 0, x / 2)) is 0 at 0, and its condition over the reals asked the + // first case to be defined there. https://github.com/asc-community/AngouriMath/issues/2256 + if (this is Piecewise piecewise) + return WhereTheCaseItTakesIsDefined(piecewise, entity => entity.DomainConditionIn(reading)).InnerSimplified; var read = Codomain > reading ? WithCodomain(reading) : this; var condition = read.IntrinsicCondition; foreach (var child in DirectChildren) diff --git a/Sources/Tests/UnitTests/Convenience/PiecewiseConditionInAReadingTest.cs b/Sources/Tests/UnitTests/Convenience/PiecewiseConditionInAReadingTest.cs new file mode 100644 index 000000000..0550f0638 --- /dev/null +++ b/Sources/Tests/UnitTests/Convenience/PiecewiseConditionInAReadingTest.cs @@ -0,0 +1,48 @@ +// +// 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.Core; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Convenience +{ + /// + /// A piecewise is defined under a reading where the case it takes is, as already + /// reads it, where asked every case to be defined: + /// tan(piecewise(pi / x provided x > 0, x / 2)) is 0 at 0, and its condition over the reals failed there. + /// #2256 + /// + [Trait("Area", "Convenience")] + public sealed class PiecewiseConditionInAReadingTest + { + [Theory] + [InlineData("tan(piecewise(pi / x provided x > 0, x / 2))", "0", true)] + [InlineData("tan(piecewise(pi / x provided x > 0, x / 2))", "-2", true)] + [InlineData("tan(piecewise(pi / x provided x > 0, x / 2))", "2", false)] + [InlineData("piecewise(arccos(1 / x) provided x > 0, sec(sgn(x)))", "-1/2", true)] + [InlineData("piecewise(arccos(1 / x) provided x > 0, sec(sgn(x)))", "1/2", false)] + [InlineData("piecewise(sqrt(x) provided x > 0, 1)", "-4", true)] + [InlineData("piecewise(sqrt(x - 1) provided x > 0, 1)", "1/2", false)] + public void OverTheRealsItIsWhereTheCaseItTakesIs(string expression, string at, bool defined) + { + var condition = expression.ToEntity().DomainConditionIn(Domain.Real).Substitute("x", at.ToEntity()).Evaled; + Assert.Equal(defined ? Entity.Boolean.True : Entity.Boolean.False, condition); + } + + /// Over the complex numbers it is what says. + [Theory] + [InlineData("tan(piecewise(pi / x provided x > 0, x / 2))", "0")] + [InlineData("tan(piecewise(pi / x provided x > 0, x / 2))", "2")] + [InlineData("piecewise(sqrt(x - 1) provided x > 0, 1)", "1/2")] + public void OverTheComplexNumbersItIsTheDomainCondition(string expression, string at) + => Assert.Equal( + expression.ToEntity().DomainCondition.Substitute("x", at.ToEntity()).Evaled, + expression.ToEntity().DomainConditionIn(Domain.Complex).Substitute("x", at.ToEntity()).Evaled); + } +}