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)")]