From f84c945951dd3569ce128544f2f6ab76431cf9ba Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Sun, 4 Oct 2026 22:19:39 +0000 Subject: [PATCH] A piecewise case whose condition is undefined is passed over A case whose predicate evaluates to NaN -- an order comparison of a number off the real line, i < 0 -- made the whole piecewise NaN, even where another case held. It holds no more than a false predicate does: the case is never taken, and the cases after it are read as before. The derivative of an antiderivative with an arm for each sign of a quantity that is not real came out NaN that way. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 15 ++++++ .../Evaluation.Omni.Classes.cs | 9 +++- .../Core/UndefinedPiecewiseConditionTest.cs | 53 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 Sources/Tests/UnitTests/Core/UndefinedPiecewiseConditionTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 070dcb02e..c66b95847 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -120,6 +120,21 @@ 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 case whose condition is undefined is passed over + +**A different answer.** A case whose predicate evaluates to NaN -- an order comparison of a number +off the real line, `i < 0`, the complex numbers not being ordered -- made the whole piecewise NaN, +even where another case held. It holds no more than a false predicate does now: the case is never +taken, and the cases after it are read as before. The derivative of an antiderivative written with +an arm for each sign of a quantity that is not real, `x^2 provided not x = 0` beside +`x^3 provided -13/10 i - 3/5 < 0`, came out NaN that way, where it is `2x`. + +| Input | Was (2.5.0) | Now | +|---|---|---| +| `"piecewise(1 provided i < 0, 2 provided 1 > 0)".ToEntity().InnerSimplified` | `NaN` | `2` | +| `"piecewise(2 * x provided not x = 0, 3 * x ^ 2 provided i - 1 < 0)".ToEntity().InnerSimplified` | `NaN` | `2 * x` where `x` is not 0 | +| `"piecewise(1 provided i < 0, 2 provided i > 0)".ToEntity().InnerSimplified` | `NaN` | `NaN`, as before: no case holds | + ### The factorial far from zero, and a quotient by a number whose modulus is nearly whole The numerical factorial is Spouge's approximation, and it holds right of zero. Used left of zero it diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Omni/Evaluation.Omni.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Omni/Evaluation.Omni.Classes.cs index 5acba0df4..1a37d9ec9 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Omni/Evaluation.Omni.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Omni/Evaluation.Omni.Classes.cs @@ -470,8 +470,15 @@ partial record Piecewise /// protected override Entity InnerSimplify(bool isExact) { + // A predicate that evaluates to NaN -- `i < 0`, the complex numbers not being + // ordered -- holds no more than a false one does: the case is never taken, and the + // cases after it are read as they would be. Kept, it made the whole piecewise NaN, + // so that `piecewise(2x provided not x = 0, 3x^2 provided i - 1 < 0)` had no value + // at any point, and the derivative of an antiderivative written with an arm for + // each sign of a quantity that is not real came out NaN where it is `2x`. foreach (var oneCase in Cases) { + if (oneCase.Predicate.Evaled.IsNaN) continue; if (oneCase.Predicate.Evaled is not Boolean) goto notYetDecidable; if (oneCase.Predicate.Evaled == Boolean.True) return oneCase.Expression.InnerSimplified(isExact); } @@ -479,7 +486,7 @@ protected override Entity InnerSimplify(bool isExact) notYetDecidable: var res = new List(); foreach (var (@case, srcCase) in (Cases, Cases.Select(c => c.New(c.Expression.InnerSimplified(isExact), c.Predicate.InnerSimplified(isExact)))).Zip()) { - if (@case.Predicate.Evaled == Boolean.False) continue; + if (@case.Predicate.Evaled == Boolean.False || @case.Predicate.Evaled.IsNaN) continue; var toAdd = srcCase.Expression is Providedf(var inner, var pred) ? new Providedf(inner, (srcCase.Predicate & pred).InnerSimplified(isExact)) : srcCase; // A piecewise takes its first matching case, and both rules below follow diff --git a/Sources/Tests/UnitTests/Core/UndefinedPiecewiseConditionTest.cs b/Sources/Tests/UnitTests/Core/UndefinedPiecewiseConditionTest.cs new file mode 100644 index 000000000..523e8e421 --- /dev/null +++ b/Sources/Tests/UnitTests/Core/UndefinedPiecewiseConditionTest.cs @@ -0,0 +1,53 @@ +// +// 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 System; +using AngouriMath; +using AngouriMath.Extensions; +using Xunit; +using static AngouriMath.Entity; + +namespace AngouriMath.Tests.Core +{ + /// + /// A piecewise case whose predicate evaluates to NaN -- an order comparison of a number off + /// the real line, i - 1 < 0 -- is never taken, as a false one is not, and the cases + /// after it are read as they would be. Kept, it made the whole piecewise NaN. + /// + [Trait("Area", "Core")] + public sealed class UndefinedPiecewiseConditionTest + { + private static void Near(double expected, Entity value) + => Assert.True(Math.Abs((double)((Number.Complex)value.EvalNumerical()).RealPart - expected) < 1e-12, $"{value} is not {expected}"); + + [Fact] + public void TheCaseIsSkippedAndTheRestKept() + { + var simplified = "piecewise(2 * x provided not x = 0, 3 * x ^ 2 provided i - 1 < 0)".ToEntity().InnerSimplified; + Assert.False(simplified.Evaled.IsNaN); + Near(0.6, simplified.Substitute("x", 0.3)); + } + + [Fact] + public void AnUndefinedCaseBeforeADecidedOneIsPassedOver() + => Assert.Equal(Number.Integer.Create(2), "piecewise(1 provided i < 0, 2 provided 1 > 0)".ToEntity().InnerSimplified); + + [Fact] + public void WithNoCaseThatHoldsItIsStillNaN() + => Assert.True("piecewise(1 provided i < 0, 2 provided i > 0)".ToEntity().InnerSimplified.Evaled.IsNaN); + + // How an antiderivative with an arm for each sign of a quantity off the real line is + // differentiated back: the arm for the negative sign is undefined there, and the + // derivative was NaN where it is that of the arm which holds. + [Fact] + 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)); + } + } +}