From e13c265b8f1dd06fa78e34d871a99a15a1890e58 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 7 Oct 2026 12:26:02 +0000 Subject: [PATCH] A piecewise is defined where the case it takes is Its domain condition asked every case's predicate to be defined, and an ordering is defined only between reals. Fixes #1817. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 16 +++++++++ .../Evaluation/Evaluation.Definition.cs | 34 +++++++++++++++++-- .../Core/UndefinedPiecewiseConditionTest.cs | 32 +++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 46d4dba8a..7a9dc5dfe 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -120,6 +120,22 @@ the working digits, or `MathS.Settings.PrecisionErrorZeroRange` where a caller h still that integer, so `e^(-123.456)` is 0 as it was. [#1338](https://github.com/asc-community/AngouriMath/issues/1338). +### A piecewise is defined where the case it takes is + +**Domain conditions that were false.** The domain condition of a piecewise asked every case's predicate +to be defined, and an ordering is defined only between reals: `piecewise(1 provided not i in RR, 2 provided i < 0)` +is `1`, and its domain condition was `false`. It is where some case's predicate holds, no earlier one does +-- a predicate with no value passed over, as the simplification passes over it -- and that case's expression +is defined. An antiderivative with an arm for each sign of a discriminant and one for its not being real +carried the discriminant's being real in its condition, and held nowhere when it was not +([#1817](https://github.com/asc-community/AngouriMath/issues/1817)). + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"piecewise(1 provided not i in RR, 2 provided i < 0)".ToEntity().DomainCondition` | `false` | `true` | +| `"piecewise(2 * x provided not x = 0, 3 * x ^ 2 provided i - 1 < 0)".ToEntity().DomainCondition` | `false` | `not x = 0` | +| `"1/(sqrt(a + i*a*x)*(c + d*x)^(3/2)*(a - i*a*x))".ToEntity().Integrate("x")` | `integral(...)`; on the unreleased master, an answer with no value at any point | 3,267 characters, defined | + ### A piecewise case whose condition is undefined is passed over **A different answer.** A case whose predicate evaluates to NaN -- an order comparison of a number diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Definition.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Definition.cs index 6db8d75a0..0e0b1d90f 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Definition.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Definition.cs @@ -42,15 +42,45 @@ partial record Entity /// - Singularities and poles (points where a function is undefined) /// - Piecewise continuity (tracking where discontinuities occur) /// - public Entity DomainCondition => domainCondition.GetValue(static @this => ScopedToItsBinder(@this, @this.DirectChildren.Aggregate(@this.IntrinsicCondition, (accum, curr) => + public Entity DomainCondition => domainCondition.GetValue(static @this => (@this is Piecewise piecewise ? WhereTheCaseItTakesIsDefined(piecewise) + : ScopedToItsBinder(@this, @this.DirectChildren.Aggregate(@this.IntrinsicCondition, (accum, curr) => (accum, curr.DomainCondition) switch { (Boolean(true), Boolean(true)) => Boolean.True, (var l, Boolean(true)) => l, (Boolean(true), var r) => r, (var l, var r) => l & r, - })), this).InnerSimplified; + }))), this).InnerSimplified; private LazyPropertyA domainCondition; + /// + /// Where a piecewise is defined: where some case's predicate holds, no earlier one does, + /// and that case's expression is defined. An earlier predicate that has no value -- i < 0, + /// the complex numbers not being ordered -- is passed over, as + /// 's simplification passes over it. + /// + /// + /// The domain condition of every child, conjoined, asked each predicate to be defined, and + /// c < 0 is defined only where c is real: piecewise(1 provided not i in RR, 2 provided i < 0) + /// is 1 and its domain condition was false, and an antiderivative with an arm for each sign of + /// a discriminant and one for its not being real held nowhere when the discriminant was not. + /// https://github.com/asc-community/AngouriMath/issues/1817 + /// + private static Entity WhereTheCaseItTakesIsDefined(Piecewise piecewise) + { + Entity defined = Boolean.False; + Entity noEarlierCase = Boolean.True; + foreach (var @case in piecewise.Cases) + { + // 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; + noEarlierCase &= predicateDomain == Boolean.True ? !@case.Predicate : !@case.Predicate | !predicateDomain; + } + return defined; + } + /// /// with each conjunct that mentions a name /// binds read the way the binder reads it: required at every diff --git a/Sources/Tests/UnitTests/Core/UndefinedPiecewiseConditionTest.cs b/Sources/Tests/UnitTests/Core/UndefinedPiecewiseConditionTest.cs index 523e8e421..9827630d1 100644 --- a/Sources/Tests/UnitTests/Core/UndefinedPiecewiseConditionTest.cs +++ b/Sources/Tests/UnitTests/Core/UndefinedPiecewiseConditionTest.cs @@ -49,5 +49,37 @@ public void TheDerivativeIsThatOfTheCaseThatHolds() var derivative = "piecewise(x ^ 2 provided not x = 0, x ^ 3 provided -13/10 * i - 3/5 < 0)".ToEntity().Differentiate("x"); Near(0.6, derivative.Substitute("x", 0.3)); } + + // Where the piecewise is defined: where the case it takes is, an undefined predicate before + // it passed over. https://github.com/asc-community/AngouriMath/issues/1817 + [Theory] + [InlineData("piecewise(1 provided not i in RR, 2 provided i < 0)", "true")] + [InlineData("piecewise(1 provided i < 0, 2 provided 1 > 0)", "true")] + [InlineData("piecewise(2 * x provided not x = 0, 3 * x ^ 2 provided i - 1 < 0)", "not x = 0")] + [InlineData("piecewise(1 provided i < 0, 2 provided i > 0)", "false")] + public void TheDomainIsWhereTheCaseItTakesIsDefined(string piecewise, string domain) + => Assert.Equal(domain.ToEntity().InnerSimplified, piecewise.ToEntity().DomainCondition); + + // Off the real line: the arm for the quantity not being real is taken, and the domain does + // not ask the comparisons in the other arms to be defined. + [Theory] + [InlineData(0.7, 1.07)] + [InlineData(-0.7, 2.0)] + public void AnArmForANonRealQuantityKeepsTheDomain(double c, double d) + { + var domain = "piecewise(1 provided not (c + i*d) in RR, 2 provided c + i*d < 0, 3 provided c + i*d > 0)".ToEntity().DomainCondition; + Assert.Equal(Entity.Boolean.True, domain.Substitute("c", c).Substitute("d", d).Evaled); + } + + // A case taken where its expression has no value leaves the piecewise undefined there, + // whatever the cases after it say. + [Fact] + public void TheFirstCaseThatHoldsDecides() + { + var domain = "piecewise(1/x provided x > -1, 0 provided true)".ToEntity().DomainCondition; + Assert.Equal(Entity.Boolean.False, domain.Substitute("x", 0).Evaled); + Assert.Equal(Entity.Boolean.True, domain.Substitute("x", -2).Evaled); + Assert.Equal(Entity.Boolean.True, domain.Substitute("x", 1).Evaled); + } } }