From fbf02e19eba7fbac31dee719c149a9391b6554f4 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 30 Oct 2026 17:53:20 +0000 Subject: [PATCH] A piecewise's condition under a reading asks only the case it takes DomainCondition reads a piecewise as defined where the case it takes is defined (#1817), but DomainConditionIn conjoined every case's condition. piecewise(sqrt(x) provided x > 0, 1) is 1 at -4, and its condition over the reals, x >= 0 and x in RR, failed there; so did tan(piecewise(pi / x provided x > 0, x / 2)) at 0, where it is 0. Over the complex numbers the two disagreed too. WhereTheCaseItTakesIsDefined now takes the condition it asks of a predicate and a case, and DomainConditionIn passes its own reading through it. PiecewiseConditionInAReadingTest takes four piecewise expressions at seven points over the reals, and three over the complex numbers, where the reading must agree with DomainCondition; four fail on master. Found by work/domaincheck. Part of #2256. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 11 +++++ Sources/.editorconfig | 3 ++ .../Evaluation/Evaluation.Definition.cs | 16 ++++++- .../PiecewiseConditionInAReadingTest.cs | 48 +++++++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 Sources/Tests/UnitTests/Convenience/PiecewiseConditionInAReadingTest.cs 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); + } +}