diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 1d512c595..d0ad7ee87 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -25,6 +25,25 @@ read first. | **Silent** | `"1/x".Integrate("x")`, and every antiderivative with a logarithm | `ln(abs(x)) + C` | `ln(x) + C` | | **Silent** | `CostModel.FewestDivisions.Cost(y ^ (1 * (-1)) * x)` | `0.007`, cheaper than `x / y` | `1.007` | | **Silent** | `Real.NaN > (Real)1`, and the other three operators | `true` | `false` | +| **Silent** | a rewritten expression under `domain(...)` — including via `Substitute` | the constraint was dropped, so it answered | it refuses, as it did before the rewrite | + +### A rewritten node keeps its `Codomain`, so a domain constraint no longer disappears + +`Entity.Replace` rebuilds every node on the path to a change, and a rebuilt node started from its +type's default codomain rather than the one the original carried. Any rewrite therefore dropped a +`domain(...)` annotation — including `Substitute`, which is built on `Replace`. + +```csharp +"domain(sqrt(x), ZZ)".ToEntity().Substitute("x", "4/9".ToEntity()).EvalNumerical() +// was: 2/3 now: NaN — 2/3 is not an integer, so the constraint refuses it +``` + +The old behaviour was **silent** and one-sided: constraints were only ever weakened, never +strengthened, so a rewrite could make an undefined expression look defined but never the reverse. +An expression that answered a value where it should have refused now refuses. + +Measured: the whole suite passes unchanged with the fix in, so nothing in the library depended on +the annotation being lost. [#955](https://github.com/asc-community/AngouriMath/issues/955). ### `Real`'s comparison operators refuse `NaN` instead of ordering it diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.AbsSignum.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.AbsSignum.Classes.cs index 3fbe93d9c..20f2ff4b7 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.AbsSignum.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.AbsSignum.Classes.cs @@ -20,7 +20,7 @@ public sealed partial record Signumf(Entity Argument) : Function, IUnaryNode public Entity NodeChild => Argument; private Signumf New(Entity arg) => - ReferenceEquals(Argument, arg) ? this : new(arg); + ReferenceEquals(Argument, arg) ? this : new(arg) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -36,7 +36,7 @@ public sealed partial record Absf(Entity Argument) : Function, IUnaryNode public Entity NodeChild => Argument; private Absf New(Entity arg) => - ReferenceEquals(Argument, arg) ? this : new(arg); + ReferenceEquals(Argument, arg) ? this : new(arg) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.ArcTrigonometry.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.ArcTrigonometry.Classes.cs index 239903e16..d6b7e110b 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.ArcTrigonometry.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.ArcTrigonometry.Classes.cs @@ -20,7 +20,7 @@ public sealed partial record Arcsinf(Entity Argument) : TrigonometricFunction, I public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible - private Arcsinf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + private Arcsinf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -36,7 +36,7 @@ public sealed partial record Arccosf(Entity Argument) : TrigonometricFunction, I public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible - private Arccosf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + private Arccosf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -52,7 +52,7 @@ public sealed partial record Arctanf(Entity Argument) : TrigonometricFunction, I public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible - private Arctanf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + private Arctanf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -68,7 +68,7 @@ public sealed partial record Arccotanf(Entity Argument) : TrigonometricFunction, public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible - Arccotanf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + Arccotanf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -84,7 +84,7 @@ public sealed partial record Arcsecantf(Entity Argument) : TrigonometricFunction public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible - private Arcsecantf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + private Arcsecantf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -100,7 +100,7 @@ public sealed partial record Arccosecantf(Entity Argument) : TrigonometricFuncti public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible - private Arccosecantf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + private Arccosecantf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs index 08896cf82..d53e4d973 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Calculus.Classes.cs @@ -22,7 +22,7 @@ public sealed partial record Derivativef(Entity Expression, Entity Var, int Iter /// Reuse the cache by returning the same object if possible private Derivativef New(Entity expression, Entity var) => ReferenceEquals(Expression, expression) && ReferenceEquals(Var, var) - ? this : new(expression, var, Iterations); + ? this : new(expression, var, Iterations) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Expression.Replace(func), Var.Replace(func))); @@ -40,7 +40,7 @@ private Integralf New(Entity expression, Entity var, (Entity from, Entity to)? r ReferenceEquals(Expression, expression) && ReferenceEquals(Var, var) && (range is null && Range is null || range is var (newFrom, newTo) && Range is var (oldFrom, oldTo) && ReferenceEquals(newFrom, oldFrom) && ReferenceEquals(newTo, oldTo)) - ? this : new(expression, var, range); + ? this : new(expression, var, range) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Expression.Replace(func), Var, Range is var (from, to) ? (from.Replace(func), to.Replace(func)) : null)); @@ -56,7 +56,7 @@ public sealed partial record Limitf(Entity Expression, Entity Var, Entity Destin /// Reuse the cache by returning the same object if possible private Limitf New(Entity expression, Entity var, Entity destination, ApproachFrom approachFrom) => ReferenceEquals(Expression, expression) && ReferenceEquals(Var, var) && ReferenceEquals(Destination, destination) - && ApproachFrom == approachFrom ? this : new(expression, var, destination, approachFrom); + && ApproachFrom == approachFrom ? this : new(expression, var, destination, approachFrom) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Expression.Replace(func), Var.Replace(func), Destination.Replace(func), ApproachFrom)); diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Exponential.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Exponential.Classes.cs index 08e5081ec..56ec3c40e 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Exponential.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Exponential.Classes.cs @@ -18,7 +18,7 @@ public sealed partial record Powf(Entity Base, Entity Exponent) : Function, IBin { /// Reuse the cache by returning the same object if possible private Powf New(Entity @base, Entity exponent) => - ReferenceEquals(Base, @base) && ReferenceEquals(Exponent, exponent) ? this : new(@base, exponent); + ReferenceEquals(Base, @base) && ReferenceEquals(Exponent, exponent) ? this : new(@base, exponent) { Codomain = Codomain }; internal override Priority Priority => Priority.Pow; /// @@ -40,7 +40,7 @@ public sealed partial record Logf(Entity Base, Entity Antilogarithm) : Function, { /// Reuse the cache by returning the same object if possible private Logf New(Entity @base, Entity antilogarithm) => - ReferenceEquals(Base, @base) && ReferenceEquals(Antilogarithm, antilogarithm) ? this : new(@base, antilogarithm); + ReferenceEquals(Base, @base) && ReferenceEquals(Antilogarithm, antilogarithm) ? this : new(@base, antilogarithm) { Codomain = Codomain }; /// public Entity NodeFirstChild => Base; diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Factorial.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Factorial.Classes.cs index 4d4b9280f..ce3ec5c3e 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Factorial.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Factorial.Classes.cs @@ -17,7 +17,7 @@ partial record Entity public sealed partial record Factorialf(Entity Argument) : Function, IUnaryNode { /// Reuse the cache by returning the same object if possible - private Factorialf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + private Factorialf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; // This is still a function for pattern replacement internal override Priority Priority => Priority.Factorial; diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Floors.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Floors.Classes.cs index de146b873..d624b2564 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Floors.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Floors.Classes.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) 2019-2026 Angouri. // AngouriMath is licensed under MIT. // Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. @@ -26,7 +26,7 @@ public sealed partial record Floorf(Entity Argument) : Function, IUnaryNode public Entity NodeChild => Argument; private Floorf New(Entity arg) => - ReferenceEquals(Argument, arg) ? this : new(arg); + ReferenceEquals(Argument, arg) ? this : new(arg) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -46,7 +46,7 @@ public sealed partial record Ceilf(Entity Argument) : Function, IUnaryNode public Entity NodeChild => Argument; private Ceilf New(Entity arg) => - ReferenceEquals(Argument, arg) ? this : new(arg); + ReferenceEquals(Argument, arg) ? this : new(arg) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Operators.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Operators.Classes.cs index fa216a635..0cdad7e8a 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Operators.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Operators.Classes.cs @@ -30,7 +30,7 @@ public static Entity Sum(IEnumerable terms) /// Reuse the cache by returning the same object if possible private Sumf New(Entity augend, Entity addend) => - ReferenceEquals(Augend, augend) && ReferenceEquals(Addend, addend) ? this : new(augend, addend); + ReferenceEquals(Augend, augend) && ReferenceEquals(Addend, addend) ? this : new(augend, addend) { Codomain = Codomain }; internal override Priority Priority => Priority.Sum; /// @@ -65,7 +65,7 @@ public sealed partial record Minusf(Entity Minuend, Entity Subtrahend) : Continu { /// Reuse the cache by returning the same object if possible private Minusf New(Entity minuend, Entity subtrahend) => - ReferenceEquals(Minuend, minuend) && ReferenceEquals(Subtrahend, subtrahend) ? this : new(minuend, subtrahend); + ReferenceEquals(Minuend, minuend) && ReferenceEquals(Subtrahend, subtrahend) ? this : new(minuend, subtrahend) { Codomain = Codomain }; internal override Priority Priority => Priority.Minus; /// @@ -99,7 +99,7 @@ public static Entity Multiply(IEnumerable terms) /// Reuse the cache by returning the same object if possible private Mulf New(Entity multiplier, Entity multiplicand) => - ReferenceEquals(Multiplier, multiplier) && ReferenceEquals(Multiplicand, multiplicand) ? this : new(multiplier, multiplicand); + ReferenceEquals(Multiplier, multiplier) && ReferenceEquals(Multiplicand, multiplicand) ? this : new(multiplier, multiplicand) { Codomain = Codomain }; internal override Priority Priority => Priority.Mul; /// @@ -134,7 +134,7 @@ public sealed partial record Divf(Entity Dividend, Entity Divisor) : ContinuousN { /// Reuse the cache by returning the same object if possible internal Divf New(Entity dividend, Entity divisor) => - ReferenceEquals(Dividend, dividend) && ReferenceEquals(Divisor, divisor) ? this : new(dividend, divisor); + ReferenceEquals(Dividend, dividend) && ReferenceEquals(Divisor, divisor) ? this : new(dividend, divisor) { Codomain = Codomain }; internal override Priority Priority => Priority.Div; /// @@ -159,7 +159,7 @@ public sealed partial record Modf(Entity Dividend, Entity Divisor) : ContinuousN { /// Reuse the cache by returning the same object if possible internal Modf New(Entity dividend, Entity divisor) => - ReferenceEquals(Dividend, dividend) && ReferenceEquals(Divisor, divisor) ? this : new(dividend, divisor); + ReferenceEquals(Dividend, dividend) && ReferenceEquals(Divisor, divisor) ? this : new(dividend, divisor) { Codomain = Codomain }; internal override Priority Priority => Priority.Mul; /// diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rounding.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rounding.Classes.cs index b6376a30d..c0e53ad41 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rounding.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Rounding.Classes.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) 2019-2026 Angouri. // AngouriMath is licensed under MIT. // Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. @@ -26,7 +26,7 @@ public sealed partial record Roundf(Entity Argument) : Function, IUnaryNode public Entity NodeChild => Argument; private Roundf New(Entity arg) => - ReferenceEquals(Argument, arg) ? this : new(arg); + ReferenceEquals(Argument, arg) ? this : new(arg) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -53,7 +53,7 @@ public sealed partial record Minf(Entity Left, Entity Right) : Function, IBinary public Entity NodeSecondChild => Right; private Minf New(Entity left, Entity right) => - ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right); + ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Left.Replace(func), Right.Replace(func))); /// @@ -73,7 +73,7 @@ public sealed partial record Maxf(Entity Left, Entity Right) : Function, IBinary public Entity NodeSecondChild => Right; private Maxf New(Entity left, Entity right) => - ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right); + ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Left.Replace(func), Right.Replace(func))); /// @@ -99,7 +99,7 @@ public sealed partial record Gcdf(Entity Left, Entity Right) : Function, IBinary public Entity NodeSecondChild => Right; private Gcdf New(Entity left, Entity right) => - ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right); + ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Left.Replace(func), Right.Replace(func))); /// diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Trigonometry.Classes.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Trigonometry.Classes.cs index 49b8b2ea7..5f5423990 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Trigonometry.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Trigonometry.Classes.cs @@ -20,7 +20,7 @@ public sealed partial record Sinf(Entity Argument) : TrigonometricFunction, IUna public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible - private Sinf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + private Sinf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -36,7 +36,7 @@ public sealed partial record Cosf(Entity Argument) : TrigonometricFunction, IUna public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible - private Cosf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + private Cosf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -52,7 +52,7 @@ public sealed partial record Tanf(Entity Argument) : TrigonometricFunction, IUna public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible - private Tanf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + private Tanf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -68,7 +68,7 @@ public sealed partial record Cotanf(Entity Argument) : TrigonometricFunction, IU public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible - private Cotanf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + private Cotanf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -84,7 +84,7 @@ public sealed partial record Secantf(Entity Argument) : TrigonometricFunction, I public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible - private Secantf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + private Secantf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -100,7 +100,7 @@ public sealed partial record Cosecantf(Entity Argument) : TrigonometricFunction, public Entity NodeChild => Argument; /// Reuse the cache by returning the same object if possible - private Cosecantf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument); + private Cosecantf New(Entity argument) => ReferenceEquals(Argument, argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// diff --git a/Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs b/Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs index e5651f2d8..4b58d85e4 100644 --- a/Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Discrete/Entity.Discrete.Classes.cs @@ -98,7 +98,7 @@ public sealed partial record Notf(Entity Argument) : Statement, IUnaryNode public Entity NodeChild => Argument; private Notf New(Entity negated) => - ReferenceEquals(Argument, negated) ? this : new(negated); + ReferenceEquals(Argument, negated) ? this : new(negated) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); /// @@ -120,7 +120,7 @@ public sealed partial record Andf(Entity Left, Entity Right) : Statement, IBinar public Entity NodeSecondChild => Right; private Andf New(Entity left, Entity right) => - ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right); + ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Left.Replace(func), Right.Replace(func))); @@ -142,7 +142,7 @@ public sealed partial record Orf(Entity Left, Entity Right) : Statement, IBinary public Entity NodeSecondChild => Right; private Orf New(Entity left, Entity right) => - ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right); + ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Left.Replace(func), Right.Replace(func))); @@ -164,7 +164,7 @@ public sealed partial record Xorf(Entity Left, Entity Right) : Statement, IBinar public Entity NodeSecondChild => Right; private Xorf New(Entity left, Entity right) => - ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right); + ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Left.Replace(func), Right.Replace(func))); @@ -186,7 +186,7 @@ public sealed partial record Impliesf(Entity Assumption, Entity Conclusion) : St public Entity NodeSecondChild => Conclusion; private Impliesf New(Entity assumption, Entity conclusion) => - ReferenceEquals(Assumption, assumption) && ReferenceEquals(Conclusion, conclusion) ? this : new(assumption, conclusion); + ReferenceEquals(Assumption, assumption) && ReferenceEquals(Conclusion, conclusion) ? this : new(assumption, conclusion) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Assumption.Replace(func), Conclusion.Replace(func))); @@ -238,7 +238,7 @@ public sealed partial record Greaterf(Entity Left, Entity Right) : ComparisonSig public Entity NodeSecondChild => Right; internal Greaterf New(Entity left, Entity right) - => ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right); + => ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Left.Replace(func), Right.Replace(func))); @@ -264,7 +264,7 @@ public sealed partial record GreaterOrEqualf(Entity Left, Entity Right) : Compar public Entity NodeSecondChild => Right; internal GreaterOrEqualf New(Entity left, Entity right) - => ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right); + => ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Left.Replace(func), Right.Replace(func))); @@ -290,7 +290,7 @@ public sealed partial record Lessf(Entity Left, Entity Right) : ComparisonSign, public Entity NodeSecondChild => Right; internal Lessf New(Entity left, Entity right) - => ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right); + => ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Left.Replace(func), Right.Replace(func))); @@ -316,7 +316,7 @@ public sealed partial record LessOrEqualf(Entity Left, Entity Right) : Compariso public Entity NodeSecondChild => Right; internal LessOrEqualf New(Entity left, Entity right) - => ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right); + => ReferenceEquals(Left, left) && ReferenceEquals(Right, right) ? this : new(left, right) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Left.Replace(func), Right.Replace(func))); @@ -343,7 +343,7 @@ public sealed partial record Inf(Entity Element, Entity SupSet) : Statement, IBi public Entity NodeSecondChild => SupSet; internal Inf New(Entity element, Entity supSet) - => ReferenceEquals(Element, element) && ReferenceEquals(SupSet, supSet) ? this : new(element, supSet); + => ReferenceEquals(Element, element) && ReferenceEquals(SupSet, supSet) ? this : new(element, supSet) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Element.Replace(func), SupSet.Replace(func))); @@ -363,7 +363,7 @@ public sealed partial record Phif(Entity Argument) : Function, IUnaryNode public Entity NodeChild => Argument; internal Phif New(Entity argument) - => ReferenceEquals(argument, Argument) ? this : new(argument); + => ReferenceEquals(argument, Argument) ? this : new(argument) { Codomain = Codomain }; /// public override Entity Replace(Func func) => func(New(Argument.Replace(func))); diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs index c8f2ec06a..4c917bb9e 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Matrix.cs @@ -57,7 +57,7 @@ private Matrix(GenTensor innerMatrix, bool toCopy) Matrix New(GenTensor innerMatrix) => innerMatrix.Iterate().All(tup => ReferenceEquals(InnerMatrix.GetValueNoCheck(tup.Index), tup.Value)) ? this - : new Matrix(innerMatrix); + : new Matrix(innerMatrix) { Codomain = Codomain }; internal override Priority Priority => Priority.Leaf; internal Matrix Elementwise(Func operation) => New(GenTensor.CreateTensor(InnerMatrix.Shape, indices => operation(InnerMatrix.GetValueNoCheck(indices)))); diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs index e5ae575be..66a722c97 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs @@ -247,12 +247,12 @@ private static bool IsALessThanB(Real A, Real B, bool closed) internal Interval New(Entity left, Entity right) => ReferenceEquals(Left, left) && ReferenceEquals(Right, right) - ? this : new Interval(left, LeftClosed, right, RightClosed); + ? this : new Interval(left, LeftClosed, right, RightClosed) { Codomain = Codomain }; internal Interval New(Entity left, bool leftClosed, Entity right, bool rightClosed) => ReferenceEquals(Left, left) && ReferenceEquals(Right, right) && LeftClosed == leftClosed && RightClosed == rightClosed - ? this : new Interval(left, leftClosed, right, rightClosed); + ? this : new Interval(left, leftClosed, right, rightClosed) { Codomain = Codomain }; /// public override Entity Replace(Func func) @@ -463,7 +463,7 @@ public override bool TryContains(Entity entity, out bool contains) internal Entity New(Entity var, Entity predicate) => ReferenceEquals(Var, var) && ReferenceEquals(Predicate, predicate) ? - this : new ConditionalSet(var, predicate); + this : new ConditionalSet(var, predicate) { Codomain = Codomain }; internal override Priority Priority => Priority.Leaf; diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Piecewise.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Piecewise.cs index 94cbd5c3e..3f759ad86 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Piecewise.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Piecewise.cs @@ -26,7 +26,7 @@ public sealed partial record Providedf(Entity Expression, Entity Predicate) : En internal Providedf New(Entity expression, Entity predicate) => ReferenceEquals(expression, Expression) && ReferenceEquals(predicate, Predicate) ? this : - new Providedf(expression, predicate); + new Providedf(expression, predicate) { Codomain = Codomain }; /// public override Entity Replace(Func func) @@ -63,7 +63,7 @@ public sealed partial record Piecewise : Entity, IEquatable protected override Entity[] InitDirectChildren() => Cases.Select(c => (c.Expression, c.Predicate)).ConcatTuples().ToArray(); private Piecewise New(IEnumerable newCases) - => (Cases, newCases).SequencesAreEqualReferences() ? this : new Piecewise(newCases); + => (Cases, newCases).SequencesAreEqualReferences() ? this : new Piecewise(newCases) { Codomain = Codomain }; /// /// Creates an instance of Piecewise diff --git a/Sources/Tests/UnitTests/Core/Domains.cs b/Sources/Tests/UnitTests/Core/Domains.cs index 2b84bc7a2..871789491 100644 --- a/Sources/Tests/UnitTests/Core/Domains.cs +++ b/Sources/Tests/UnitTests/Core/Domains.cs @@ -6,6 +6,7 @@ // using AngouriMath; +using AngouriMath.Core; using AngouriMath.Extensions; using Xunit; @@ -26,6 +27,42 @@ public sealed class Domains public void CheckNaN(string expr) => Assert.Equal(MathS.NaN, expr.EvalNumerical()); + /// + /// A rewritten node keeps the codomain the original carried. + /// https://github.com/asc-community/AngouriMath/issues/955 -- `Replace` rebuilds every + /// node on the path to a change, and the rebuilt node used to start from its type's + /// default, so a domain constraint silently disappeared and the expression began + /// answering where it had refused. + /// + [Fact] + public void ARewrittenNodeKeepsItsCodomain() + { + var original = "domain(sqrt(x), ZZ)".ToEntity(); + Assert.Equal(Domain.Integer, original.Codomain); + + var rewritten = original.Replace(node => + node is Entity.Variable { Name: "x" } ? "y".ToEntity() : node); + Assert.Equal(Domain.Integer, rewritten.Codomain); + } + + /// + /// The same through `Substitute`, which is what a caller actually reaches for and is + /// built on `Replace`. sqrt(4/9) is 2/3, which is not an integer, so this must refuse. + /// + [Fact] + public void SubstitutingIntoADomainKeepsTheConstraint() + => Assert.Equal(MathS.NaN, + "domain(sqrt(x), ZZ)".ToEntity().Substitute("x", "4/9".ToEntity()).EvalNumerical()); + + /// And a constraint deeper than one level survives too. + [Fact] + public void ANestedRewriteKeepsTheCodomain() + { + var rewritten = "domain(sin(x) + 1, ZZ)".ToEntity() + .Replace(node => node is Entity.Variable { Name: "x" } ? "y".ToEntity() : node); + Assert.Equal(Domain.Integer, rewritten.Codomain); + } + [Theory] [InlineData("domain(sqrt(4), RR)")] [InlineData("domain(sqrt(4), QQ)")]