From b355729743899eb3d9ef1b1a641dc98505064fba Mon Sep 17 00:00:00 2001 From: Hadrian Tang Date: Tue, 13 Jan 2026 05:45:27 +0800 Subject: [PATCH 1/2] Do not simplify x in {1} to false --- Sources/AngouriMath/Convenience/MathS.cs | 2 +- .../Entity.Continuous.Complex.Definition.cs | 5 - .../Entity.Continuous.Real.Definition.cs | 2 +- .../Entity/Continuous/Number/Operators.cs | 2 + .../Core/Entity/Entity.Definition.cs | 5 + .../Core/Entity/Omni/Entity.Omni.Classes.cs | 42 +- .../Entity/Omni/Entity.Omni.Definition.cs | 5 +- .../Omni/Sets/SetOperators.Intersection.cs | 2 - .../Omni/Sets/SetOperators.SetSubtraction.cs | 4 +- .../Functions/Continuous/Differentiation.cs | 2 +- .../Limits/Solvers/Limit.Solvers.cs | 2 +- .../Limits/Solvers/Solvers.Definition.cs | 2 +- .../Continuous/Solvers/EquationSolver.cs | 11 + .../AnalyticalEquationSolver.cs | 30 +- .../EquationSolver/InvertNode.Classes.cs | 22 +- .../Solvers/EquationSolver/SolveStatement.cs | 5 +- .../AnalyticalInequalitySolver.cs | 7 +- .../Solvers/SetSolver/AnalyticalSetSolver.cs | 13 +- .../Evaluation/Evaluation.Classes.cs | 88 ++-- ...aluation.Continuous.Arithmetics.Classes.cs | 254 ++++-------- .../Evaluation.Continuous.Calculus.Classes.cs | 91 +---- ...luation.Continuous.Trigonometry.Classes.cs | 284 +++++-------- .../Evaluation/Evaluation.Definition.cs | 159 ++++---- .../Evaluation.Discrete.Classes.cs | 377 ++++-------------- .../Evaluation.Omni.Classes.cs | 215 +++------- .../Functions/Simplification/LiftProvided.cs | 175 -------- .../Patterns/Patterns.EqualityInequality.cs | 2 +- .../Functions/Simplification/Simplificator.cs | 2 - .../SolveTest/SolveEquationWithPiecewise.cs | 29 +- .../UnitTests/Common/InnerSimplifyTest.cs | 48 +-- Sources/Tests/UnitTests/Core/Sets/Contains.cs | 38 +- .../Discrete/EqualityInequalityEval.cs | 31 ++ Sources/Tests/UnitTests/Discrete/InSet.cs | 14 +- .../UnitTests/PatternsTest/SetSimplify.cs | 11 +- 34 files changed, 657 insertions(+), 1324 deletions(-) delete mode 100644 Sources/AngouriMath/Functions/Simplification/LiftProvided.cs diff --git a/Sources/AngouriMath/Convenience/MathS.cs b/Sources/AngouriMath/Convenience/MathS.cs index 7886a45dd..41ed419f4 100644 --- a/Sources/AngouriMath/Convenience/MathS.cs +++ b/Sources/AngouriMath/Convenience/MathS.cs @@ -3324,7 +3324,7 @@ public static (Variable, Variable, Variable, Variable, Variable) Var(string name // Undefined /// - /// That is both undefined and indeterminite + /// NaN represents both "undefined" and "indeterminate". /// Any operation on NaN returns NaN /// /// diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Complex.Definition.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Complex.Definition.cs index 3ba1032a6..fa4fc2552 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Complex.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Complex.Definition.cs @@ -71,11 +71,6 @@ private protected Complex(Real? real, Real? imaginary) => /// public new bool IsZero => RealPart.EDecimal.IsZero && ImaginaryPart.EDecimal.IsZero; - /// - /// Checks whether the given number is undefined - /// - public bool IsNaN => this == Real.NaN; - /// /// Creates an instance of Complex /// diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Real.Definition.cs b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Real.Definition.cs index 99bf610b4..d733c1e27 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Real.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Entity.Continuous.Real.Definition.cs @@ -28,7 +28,7 @@ public partial record Real : Complex, System.IComparable private protected Real(EDecimal @decimal) : base(null, null) => EDecimal = @decimal; /// - /// The PeterO number representation in decimal + /// The PeterO number representation in /// public EDecimal EDecimal { get; } diff --git a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs index c739e1e58..41017aa57 100644 --- a/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs +++ b/Sources/AngouriMath/Core/Entity/Continuous/Number/Operators.cs @@ -373,6 +373,8 @@ static Complex BinaryIntPow(Complex num, EInteger val) if (@base is Real { EDecimal: { IsNegative: false } realBase } && power is Real { EDecimal: var realPower }) return realBase.Pow(realPower, context); // From https://source.dot.net/#System.Runtime.Numerics/System/Numerics/Complex.cs,7dc9c2ee4f99814a + // NOTE: System.Numerics.Complex.Pow(0, System.Numerics.Complex(-2, 1)) gives 0 + 0i despite being mathematically undefined + // NOTE: System.Numerics.Complex.Pow(0, 0) gives 1 + 0i despite being mathematically undefined var baseReal = @base.RealPart.EDecimal; var baseImaginary = @base.ImaginaryPart.EDecimal; var powerReal = power.RealPart.EDecimal; diff --git a/Sources/AngouriMath/Core/Entity/Entity.Definition.cs b/Sources/AngouriMath/Core/Entity/Entity.Definition.cs index 8e827b6dd..5400575a2 100644 --- a/Sources/AngouriMath/Core/Entity/Entity.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Entity.Definition.cs @@ -705,5 +705,10 @@ public IReadOnlyList Vars /// /// public bool IsConstantLeaf => this is Boolean or Number or Set.SpecialSet; + + /// + /// Checks whether this entity represents the undefined value (). + /// + public bool IsNaN => this == Real.NaN; } } diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs index 85a18ea98..25cd53eac 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Classes.cs @@ -14,6 +14,7 @@ using HonkSharp.Laziness; using Complex = AngouriMath.Entity.Number.Complex; using System.Linq.Expressions; +using System.Diagnostics.CodeAnalysis; namespace AngouriMath { @@ -53,7 +54,7 @@ public FiniteSet Apply(Func func) var changed = func(el); if (!ReferenceEquals(changed, el)) hasAnythingChanged = true; - newElements.Add(changed); + if (changed != MathS.NaN) newElements.Add(changed); } if (hasAnythingChanged) return newElements.ToFiniteSet(); @@ -123,31 +124,26 @@ internal static FiniteSet Unite(FiniteSet A, FiniteSet B) => new FiniteSet(A.Elements.Concat(B.Elements)); // It could be written with one chain request, but readability > one line - internal static FiniteSet Subtract(FiniteSet A, FiniteSet B) + internal static bool TryFullSubtract(FiniteSet A, FiniteSet B, [NotNullWhen(true)] out FiniteSet? resultSet) { + var constantSets = A.Vars.Count == 0 && B.Vars.Count == 0; var dict = BuildDictionaryFromElements(A.Elements, noCheck: true); foreach (var el in B) - dict.Remove(el.Evaled); - return new FiniteSet(dict.Values, noCheck: true); // we didn't add anything - } - - internal static FiniteSet Intersect(FiniteSet A, FiniteSet B) - { - var dict = BuildDictionaryFromElements(A.Elements, noCheck: true); - foreach (var el in A.elements) - if (!B.Contains(el.Key)) - dict.Remove(el.Key); - return new FiniteSet(dict.Values, noCheck: true); // we didn't add anything + if (!dict.Remove(el.Evaled) && !constantSets) { resultSet = null; return false; } + resultSet = new FiniteSet(dict.Values, noCheck: true); // we didn't add anything + return true; } /// public override bool TryContains(Entity entity, out bool contains) { + if (IsSetEmpty) { contains = false; return true; } // a in {} is unambiguously false contains = elements.ContainsKey(entity.Evaled); - // a in { 2, 3 } is false - // 4 in { a, 3 } is false - // TODO: should we return false when there are symbolic expressions? - return true; + // x in { x, 2 } and 2 in { x, 2 } are both unambiguously true + if (contains) return true; + // however, 1 in { x, 2 }, y in { x, 2 } are both unknown + // meanwhile, 2 in { 1, 3 } is unambiguously false + return entity.Vars.Count == 0 && elements.Keys.All(el => el.Vars.Count == 0); } /// @@ -168,7 +164,7 @@ public bool Equals(FiniteSet? other) if (other.Count != Count) return false; foreach (var pair in elements) - if (!other.Contains(pair.Key)) + if (!other.TryContains(pair.Key, out var contains) || !contains) return false; return true; } @@ -653,13 +649,9 @@ public override Entity Replace(Func func) public override bool TryContains(Entity entity, out bool contains) { contains = false; - if (Left is not Set left || Right is not Set right) - return false; - if (left.TryContains(entity, out var leftContains) && right.TryContains(entity, out var rightContains)) - { - contains = leftContains || rightContains; - return true; - } + if (Left is not Set left || Right is not Set right) return false; + if (left.TryContains(entity, out contains) && contains) return true; + if (right.TryContains(entity, out contains)) return true; return false; } diff --git a/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Definition.cs b/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Definition.cs index 28f47286c..d717b9ee6 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Definition.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Entity.Omni.Definition.cs @@ -22,13 +22,12 @@ public Entity In(Entity supSet) => new Inf(this, supSet); /// - /// Creates a node of a expression assuming some condition + /// Creates a node of a expression assuming some condition. If the condition is the true node, the node itself is returned. /// /// /// A condition under which a given expression (this) is valid. /// /// A node - public Entity Provided(Entity that) - => new Providedf(this, that); + public Entity Provided(Entity that) => that == Boolean.True ? this : new Providedf(this, that); } } diff --git a/Sources/AngouriMath/Core/Entity/Omni/Sets/SetOperators.Intersection.cs b/Sources/AngouriMath/Core/Entity/Omni/Sets/SetOperators.Intersection.cs index 856c497a1..03e630b9d 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Sets/SetOperators.Intersection.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Sets/SetOperators.Intersection.cs @@ -14,8 +14,6 @@ internal static partial class SetOperators { internal static Set IntersectFiniteSetAndSet(FiniteSet finite, Set set) { - if (set is FiniteSet another) - return FiniteSet.Intersect(finite, another); var fsb = new FiniteSetBuilder(); var amb = new FiniteSetBuilder(); foreach (var elem in finite) diff --git a/Sources/AngouriMath/Core/Entity/Omni/Sets/SetOperators.SetSubtraction.cs b/Sources/AngouriMath/Core/Entity/Omni/Sets/SetOperators.SetSubtraction.cs index d028ce0a7..4f98c61bb 100644 --- a/Sources/AngouriMath/Core/Entity/Omni/Sets/SetOperators.SetSubtraction.cs +++ b/Sources/AngouriMath/Core/Entity/Omni/Sets/SetOperators.SetSubtraction.cs @@ -14,8 +14,8 @@ internal static partial class SetOperators { internal static Set SetSubtractSetAndFiniteSet(Set set, FiniteSet finite) { - if (set is FiniteSet another) - return FiniteSet.Subtract(another, finite); + if (set is FiniteSet another && FiniteSet.TryFullSubtract(another, finite, out var result)) + return result; var fsb = new FiniteSetBuilder(finite); foreach (var el in finite) if (set.TryContains(el, out var contains) && !contains) diff --git a/Sources/AngouriMath/Functions/Continuous/Differentiation.cs b/Sources/AngouriMath/Functions/Continuous/Differentiation.cs index 4e1d47140..299f4c25f 100644 --- a/Sources/AngouriMath/Functions/Continuous/Differentiation.cs +++ b/Sources/AngouriMath/Functions/Continuous/Differentiation.cs @@ -136,7 +136,7 @@ partial record Powf /// protected override Entity InnerDifferentiate(Variable variable) => Exponent == Integer.One - ? Base.InnerDifferentiate(variable).WithCondition(Base.DomainCondition) // don't create x^0 which is undefined for x=0 + ? Base.InnerDifferentiate(variable).Provided(Base.DomainCondition) // don't create x^0 which is undefined for x=0 : Exponent is Number exp ? exp * Base.Pow(exp - 1) * Base.InnerDifferentiate(variable) : Base is Number diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs index 67415c3cd..479cbb8ec 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs @@ -157,7 +157,7 @@ internal static class LimitSolvers var divEvaled = div.Evaled; return divEvaled switch { - { Evaled: Complex { IsNaN: true } } => null, + { Evaled.IsNaN: true } => null, { } res when res.ContainsNode("+oo") || res.ContainsNode("-oo") => div.InnerSimplified, { Evaled: Complex } limit => limit, _ => upperLogLimit / lowerLogLimit, diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs index 3bcd6b32d..7766b4a50 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs @@ -88,7 +88,7 @@ private static Entity ExpandLogarithm(Entity expr) { _ when !expr.ContainsNode(x) => expr, // avoid NaN values as non finite numbers - Real { IsNaN: true } => Real.NaN, + { IsNaN: true } => MathS.NaN, // if x -> -oo just make -x -> +oo Real { IsFinite: false, IsNegative: true } => SimplifyAndComputeLimitToInfinity(expr.Substitute(x, -x), x), // compute limit for x -> +oo diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver.cs index 230a2bbde..0f1b9b176 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver.cs @@ -103,5 +103,16 @@ internal static List> InSolveSystem(List equations, ReadOnl } return result; } + internal static Set SolvePiecewise(Piecewise piecewise, Variable x, Func solve) + { + Entity cond = true; + var res = new List(); + foreach (var c in piecewise.Cases) + { + res.Add(solve(c.Expression, x).Filter(c.Predicate & cond, x)); + cond &= !c.Predicate; + } + return res.Unite(); + } } } \ No newline at end of file diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs index 2c03b9318..35fe9f2fb 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs @@ -5,12 +5,13 @@ // Website: https://am.angouri.org. // -using static AngouriMath.Entity; -using AngouriMath.Functions.Algebra; -using AngouriMath.Extensions; -using static AngouriMath.Entity.Set; using AngouriMath.Core.Exceptions; using AngouriMath.Core.Multithreading; +using AngouriMath.Extensions; +using AngouriMath.Functions.Algebra; +using System.Linq.Expressions; +using static AngouriMath.Entity; +using static AngouriMath.Entity.Set; namespace AngouriMath { @@ -76,17 +77,6 @@ namespace AngouriMath.Functions.Algebra.AnalyticalSolving { internal static class AnalyticalEquationSolver { - private static Set PiecewiseIntoIndependentEquations(Piecewise leftPart, Variable x, Entity rightPart) - { - Entity cond = true; - var res = new List(); - foreach (var c in leftPart.Cases) - { - res.Add(Solve(c.Expression - rightPart, x).Filter(c.Predicate & cond, x)); - cond &= !c.Predicate; - } - return res.Unite(); - } /// Equation solver /// @@ -98,12 +88,10 @@ private static Set PiecewiseIntoIndependentEquations(Piecewise leftPart, Variabl /// Variable to solve over internal static Set Solve(Entity expr, Variable x, bool compensateSolving = false) { + expr = expr.InnerSimplified; if (expr == x) return new Entity[] { 0 }.ToSet(); - if (ProvidedLifter.ExtractProvidedPredicates(ref expr, out var predicate)) - return ProvidedLifter.MergePredicateIntoSolveResult(Solve(expr, x, compensateSolving), x, predicate); - // Applies an attempt to downcast roots static Entity TryDowncast(Entity equation, Variable x, Entity root) { @@ -131,8 +119,6 @@ static Entity TryDowncast(Entity equation, Variable x, Entity root) case Minusf(var subtrahend, var minuend) when !minuend.ContainsNode(x) && compensateSolving: if (subtrahend == x) return new[] { minuend }.ToSet(); - if (subtrahend is Piecewise piecewise) - return PiecewiseIntoIndependentEquations(piecewise, x, minuend); Entity? lastChild = null; foreach (var child in subtrahend.DirectChildren) if (child.ContainsNode(x)) @@ -145,6 +131,10 @@ static Entity TryDowncast(Entity equation, Variable x, Entity root) return subtrahend.Invert(minuend, lastChild).Select(result => Solve(lastChild - result, x, compensateSolving: true)).Unite(); case Function: return expr.Invert(0, x).Select(ent => TryDowncast(expr, x, ent)).ToSet(); + case Providedf(var expression, var predicate): + return Solve(expression, x, compensateSolving).Filter(predicate, x); + case Piecewise p: + return EquationSolver.SolvePiecewise(p, x, (e, x) => Solve(e, x, compensateSolving)); default: break; } diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs index a6d19c3ef..6b8cc52b8 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/InvertNode.Classes.cs @@ -441,19 +441,19 @@ private protected override IEnumerable InvertNode(Entity value, Entity x var (withX, withoutX) = Left.ContainsNode(x) ? (Left, Right) : (Right, Left); if (value is FiniteSet valueFiniteSet && withoutX is FiniteSet A) { - if (!A.TryIsSubsetOf(valueFiniteSet, out var isSub)) - return withX.InvertNode(value.SetSubtract(withoutX), x); - if (!isSub) - return Empty; - var sub = FiniteSet.Subtract(valueFiniteSet, A); - var answers = new List(); - foreach (var ans in A.GetPowerSet()) + if (A.TryIsSubsetOf(valueFiniteSet, out var isSub) && isSub && + FiniteSet.TryFullSubtract(valueFiniteSet, A, out var sub)) { - if (ans is not FiniteSet finiteSet) - throw new AngouriBugException("PowerSet must return a set of sets"); - answers.AddRange(withX.InvertNode(FiniteSet.Unite(sub, finiteSet), x)); + var answers = new List(); + foreach (var ans in A.GetPowerSet()) + { + if (ans is not FiniteSet finiteSet) + throw new AngouriBugException("PowerSet must return a set of sets"); + answers.AddRange(withX.InvertNode(FiniteSet.Unite(sub, finiteSet), x)); + } + return answers; } - return answers; + else return Empty; } return withX.InvertNode(value.SetSubtract(withoutX), x); } diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/SolveStatement.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/SolveStatement.cs index 096cab528..dff4d64dc 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/SolveStatement.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/SolveStatement.cs @@ -54,8 +54,9 @@ internal static Set Solve(Entity expr, Variable x) Variable when expr == x => new FiniteSet(true), Inf(var var, Set set) when var == x => set, - - Providedf(var inner, var predicate) => ProvidedLifter.MergePredicateIntoSolveResult(Solve(inner, x), x, predicate), + + Providedf(var e, var predicate) => Solve(e, x).Filter(predicate, x), + Piecewise p => EquationSolver.SolvePiecewise(p, x, Solve), // TODO: Although piecewise needed? _ => Set.Empty diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/InequalitySolver/AnalyticalInequalitySolver.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/InequalitySolver/AnalyticalInequalitySolver.cs index 462164831..b4a85035d 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/InequalitySolver/AnalyticalInequalitySolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/InequalitySolver/AnalyticalInequalitySolver.cs @@ -18,8 +18,11 @@ internal static class AnalyticalInequalitySolver /// internal static Set Solve(Entity expr, Variable x) { - if (ProvidedLifter.ExtractProvidedPredicates(ref expr, out var predicate)) - return ProvidedLifter.MergePredicateIntoSolveResult(Solve(expr, x), x, predicate); + switch (expr) + { + case Providedf(var e, var predicate): return Solve(e, x).Filter(predicate, x); + case Piecewise p: return EquationSolver.SolvePiecewise(p, x, Solve); + } { if (MathS.Utils.TryGetPolyLinear(expr, x, out var a, out var b)) { diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/SetSolver/AnalyticalSetSolver.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/SetSolver/AnalyticalSetSolver.cs index b60ac99cb..a1bb1d033 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/SetSolver/AnalyticalSetSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/SetSolver/AnalyticalSetSolver.cs @@ -6,6 +6,7 @@ // using AngouriMath.Extensions; +using AngouriMath.Functions.Algebra; using static AngouriMath.Entity; using static AngouriMath.Entity.Set; @@ -15,10 +16,14 @@ internal static class AnalyticalSetSolver { internal static Set Solve(Entity left, Entity right, Variable x) { - if (ProvidedLifter.ExtractProvidedPredicates(ref left, out var predicate)) - return ProvidedLifter.MergePredicateIntoSolveResult(Solve(left, right, x), x, predicate); - if (ProvidedLifter.ExtractProvidedPredicates(ref right, out predicate)) - return ProvidedLifter.MergePredicateIntoSolveResult(Solve(left, right, x), x, predicate); + switch (left, right) + { + case (Providedf(var l, var predicate), _): return Solve(l, right, x).Filter(predicate, x); + case (_, Providedf(var r, var predicate)): return Solve(left, r, x).Filter(predicate, x); + case (Piecewise p, _): return EquationSolver.SolvePiecewise(p, x, (e, x) => Solve(e, right, x)); + case (_, Piecewise p): return EquationSolver.SolvePiecewise(p, x, (e, x) => Solve(left, e, x)); + } + left = left.Replace(Patterns.SetOperatorRules); right = right.Replace(Patterns.SetOperatorRules); if (left.DirectChildren.Count(c => c == x) diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs index b3d66fd2b..97f065302 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Classes.cs @@ -17,10 +17,7 @@ public partial record Variable { private protected override Entity IntrinsicCondition => true; /// - protected override Entity InnerEval() => ConstantList.TryGetValue(Name, out var value) ? value : this; - - /// - protected override Entity InnerSimplify() => this; + protected override Entity InnerSimplify(bool isExact) => !isExact && ConstantList.TryGetValue(Name, out var value) ? value : this; } /// @@ -42,23 +39,25 @@ public partial record Variable /// If no suitable case in switch found, it should return the default node, for example, for sum it would be /// (a, b) => a + b /// - /// + /// /// Check if the number is exact and, if so, return it. /// - /// - /// If check exact is enabled, then NaN will be exact if this - /// param is true ; it won't otherwise + /// + /// Set operations should not be applied on all pairs of elements when it cannot be simplified. /// private Entity ExpandOnTwoArguments( Entity left, Entity right, Func operation, Func defaultCtor, - bool checkIfExactEvaled = false, - bool allowNaNAsExact = true) + bool isExact, + bool propagateSet = true) { - if (checkIfExactEvaled && this.Evaled is Number { IsExact: true } n && (allowNaNAsExact || n != MathS.NaN) ) + if (isExact && this.Evaled is (Number { IsExact: true } or Boolean) and var n) return n; + left = left.InnerSimplified(isExact); + right = right.InnerSimplified(isExact); + if (left.IsNaN || right.IsNaN) return MathS.NaN; if (operation(left, right) is { } preRes) return preRes; @@ -67,7 +66,7 @@ Entity ops(Entity a, Entity b) { if (operation(a, b) is { } res) return res; - if (checkIfExactEvaled && defaultCtor(this, a, b).Evaled is Number { IsExact: true } n) + if (isExact && defaultCtor(this, a, b).Evaled is Number { IsExact: true } n) return n; return defaultCtor(this, a, b); } @@ -75,14 +74,14 @@ Entity ops(Entity a, Entity b) return (left, right) switch { (Providedf a, Providedf b) => ops(a.Expression, b.Expression).Provided(a.Predicate & b.Predicate), - (Providedf a, var b) => ExpandOnTwoArguments(a.Expression, b, operation, defaultCtor, checkIfExactEvaled).Provided(a.Predicate), - (var a, Providedf b) => ExpandOnTwoArguments(a, b.Expression, operation, defaultCtor, checkIfExactEvaled).Provided(b.Predicate), - (Piecewise a, Piecewise b) => + (Providedf a, var b) => ExpandOnTwoArguments(a.Expression, b, operation, defaultCtor, isExact).Provided(a.Predicate), + (var a, Providedf b) => ExpandOnTwoArguments(a, b.Expression, operation, defaultCtor, isExact).Provided(b.Predicate), + (Piecewise a, Piecewise b) => MathS.Piecewise( - (a.Cases, b.Cases).EachForEach((c1, c2) => + (a.Cases, b.Cases).EachForEach((c1, c2) => ( - ExpandOnTwoArguments(c1.Expression, c2.Expression, operation, defaultCtor, checkIfExactEvaled) + ExpandOnTwoArguments(c1.Expression, c2.Expression, operation, defaultCtor, isExact) , (c1.Predicate & c2.Predicate).InnerSimplified).ToProvided() ) @@ -92,18 +91,23 @@ Entity ops(Entity a, Entity b) (Matrix a, Matrix b) => a.InnerMatrix.Shape == b.InnerMatrix.Shape ? a.Elementwise(b, ops) : defaultCtor(this, left, right), (Matrix a, var b) => a.Elementwise(a => ops(a, b)), (var a, Matrix b) => b.Elementwise(b => ops(a, b)), - (FiniteSet a, FiniteSet b) => new FiniteSet((a, b).EachForEach().Select(s => ops(s.left, s.right))), - (FiniteSet a, var b) => a.Apply(a => ops(a, b)), - (var a, FiniteSet b) => b.Apply(b => ops(a, b)), - _ => defaultCtor(this, left, right) + _ => propagateSet ? (left, right) switch + { + (FiniteSet a, FiniteSet b) => new FiniteSet((a, b).EachForEach().Select(s => ops(s.left, s.right))), + (FiniteSet a, var b) => a.Apply(a => ops(a, b)), + (var a, FiniteSet b) => b.Apply(b => ops(a, b)), + _ => defaultCtor(this, left, right) + } : defaultCtor(this, left, right) }; } - private Entity ExpandOnOneArgument(Entity expr, Func operation, Func defaultCtor, bool checkIfExactEvaled = false) + private Entity ExpandOnOneArgument(Entity expr, Func operation, Func defaultCtor, bool isExact, + bool propagateSet = true) { - if (checkIfExactEvaled && this.Evaled is Number { IsExact: true } n) + if (isExact && this.Evaled is (Number { IsExact: true } or Boolean) and var n) return n; + expr = expr.InnerSimplified(isExact); if (operation(expr) is { } notNull) return notNull; @@ -111,26 +115,32 @@ Entity ops(Entity a) { if (operation(a) is { } res) return res; - if (checkIfExactEvaled && defaultCtor(this, a).Evaled is Number { IsExact: true } n) + if (isExact && defaultCtor(this, a).Evaled is Number { IsExact: true } n) return n; return defaultCtor(this, a); } return expr switch { - Providedf p => ExpandOnOneArgument(p.Expression, operation, defaultCtor, checkIfExactEvaled).Provided(p.Predicate), + Providedf p => ExpandOnOneArgument(p.Expression, operation, defaultCtor, isExact).Provided(p.Predicate), Piecewise p => p.ApplyToValues(ops), Matrix t => t.Elementwise(ops), - FiniteSet s => s.Apply(ops), - _ => defaultCtor(this, expr) + _ => propagateSet ? expr switch + { + FiniteSet s => s.Apply(ops), + _ => defaultCtor(this, expr) + } : defaultCtor(this, expr) }; } - private Entity ExpandOnTwoAndTArguments(Entity left, Entity right, T third, Func operation, Func defaultCtor, bool checkIfExactEvaled = false) + private Entity ExpandOnTwoAndTArguments(Entity left, Entity right, T third, Func operation, Func defaultCtor, bool isExact, + bool propagateSet = true) { - if (checkIfExactEvaled && this.Evaled is Number { IsExact: true } n) + if (isExact && this.Evaled is (Number { IsExact: true } or Boolean) and var n) return n; + left = left.InnerSimplified(isExact); + right = right.InnerSimplified(isExact); if (operation(left, right, third) is { } preRes) return preRes; @@ -138,7 +148,7 @@ Entity ops(Entity a, Entity b) { if (operation(a, b, third) is { } res) return res; - if (checkIfExactEvaled && defaultCtor(this, a, b, third).Evaled is Number { IsExact: true } n) + if (isExact && defaultCtor(this, a, b, third).Evaled is Number { IsExact: true } n) return n; return defaultCtor(this, a, b, third); } @@ -146,14 +156,14 @@ Entity ops(Entity a, Entity b) return (left, right, third) switch { (Providedf a, Providedf b, _) => ops(a.Expression, b.Expression).Provided(a.Predicate & b.Predicate), - (Providedf a, var b, _) => ExpandOnTwoAndTArguments(a.Expression, b, third, operation, defaultCtor, checkIfExactEvaled).Provided(a.Predicate), - (var a, Providedf b, _) => ExpandOnTwoAndTArguments(a, b.Expression, third, operation, defaultCtor, checkIfExactEvaled).Provided(b.Predicate), + (Providedf a, var b, _) => ExpandOnTwoAndTArguments(a.Expression, b, third, operation, defaultCtor, isExact).Provided(a.Predicate), + (var a, Providedf b, _) => ExpandOnTwoAndTArguments(a, b.Expression, third, operation, defaultCtor, isExact).Provided(b.Predicate), (Piecewise a, Piecewise b, _) => MathS.Piecewise( (a.Cases, b.Cases).EachForEach((c1, c2) => ( - ExpandOnTwoAndTArguments(c1.Expression, c2.Expression, third, operation, defaultCtor, checkIfExactEvaled) + ExpandOnTwoAndTArguments(c1.Expression, c2.Expression, third, operation, defaultCtor, isExact) , (c1.Predicate & c2.Predicate).InnerSimplified).ToProvided() ) @@ -163,10 +173,14 @@ Entity ops(Entity a, Entity b) (Matrix a, Matrix b, _) => a.InnerMatrix.Shape == b.InnerMatrix.Shape ? a.Elementwise(b, ops) : defaultCtor(this, left, right, third), (Matrix a, var b, _) => a.Elementwise(a => ops(a, b)), (var a, Matrix b, _) => b.Elementwise(b => ops(a, b)), - (FiniteSet a, FiniteSet b, _) => new FiniteSet((a, b).EachForEach().Select(s => ops(s.left, s.right))), - (FiniteSet a, var b, _) => a.Apply(a => ops(a, b)), - (var a, FiniteSet b, _) => b.Apply(b => ops(a, b)), - _ => defaultCtor(this, left, right, third) + _ => propagateSet ? (left, right, third) switch + { + (FiniteSet a, FiniteSet b, _) => new FiniteSet((a, b).EachForEach().Select(s => ops(s.left, s.right))), + (FiniteSet a, var b, _) => a.Apply(a => ops(a, b)), + (var a, FiniteSet b, _) => b.Apply(b => ops(a, b)), + _ => defaultCtor(this, left, right, third) + } + : defaultCtor(this, left, right, third) }; } } diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs index 36ebea728..10071ad4f 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Arithmetics.Classes.cs @@ -17,9 +17,7 @@ public partial record Number private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() => this; - /// - protected override Entity InnerSimplify() => this; + protected override Entity InnerSimplify(bool isExact) => this; } // Each function and operator processing @@ -27,131 +25,80 @@ public partial record Sumf { private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() => - ExpandOnTwoArguments(Augend.Evaled, Addend.Evaled, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnTwoArguments(Augend, Addend, (augend, addend) => (augend, addend) switch { - (Complex a, Complex b) => a + b, - (Interval inter, var n2) when n2 is not Set => inter.New((inter.Left + n2).Evaled, (inter.Right + n2).Evaled), - (var n2, Interval inter) when n2 is not Set => inter.New((n2 + inter.Left).Evaled, (n2 + inter.Right).Evaled), + (Complex a, Complex b) when !isExact => a + b, + (var n1, Integer(0)) => n1, + (Integer(0), var n2) => n2, + (Interval inter, var n2) when n2 is not Set => inter.New((inter.Left + n2).InnerSimplified(isExact), (inter.Right + n2).InnerSimplified(isExact)), + (var n2, Interval inter) when n2 is not Set => inter.New((n2 + inter.Left).InnerSimplified(isExact), (n2 + inter.Right).InnerSimplified(isExact)), _ => null }, - (@this, a, b) => ((Sumf)@this).New(a, b) - ); - - - /// - protected override Entity InnerSimplify() => - ExpandOnTwoArguments(Augend.InnerSimplified, Addend.InnerSimplified, - (augend, addend) => (augend, addend) switch - { - (Complex a, Complex b) => a + b, - (var n1, Integer(0)) => n1, - (Integer(0), var n2) => n2, - (Interval inter, var n2) when n2 is not Set => inter.New((inter.Left + n2).InnerSimplified, (inter.Right + n2).InnerSimplified), - (var n2, Interval inter) when n2 is not Set => inter.New((n2 + inter.Left).InnerSimplified, (n2 + inter.Right).InnerSimplified), - _ => null - }, - (@this, a, b) => ((Sumf)@this).New(a, b), - true); + (@this, a, b) => ((Sumf)@this).New(a, b), isExact); } public partial record Minusf { private protected override Entity IntrinsicCondition => Boolean.True; - /// - protected override Entity InnerEval() => ExpandOnTwoArguments(Subtrahend.Evaled, Minuend.Evaled, - (augend, addend) => (augend, addend) switch - { - (Complex a, Complex b) => a - b, - (Interval inter, var n2) when n2 is not Set => inter.New((inter.Left - n2).Evaled, (inter.Right - n2).Evaled), - (var n2, Interval inter) when n2 is not Set => inter.New((n2 - inter.Left).Evaled, (n2 - inter.Right).Evaled), - _ => null - }, - (@this, a, b) => ((Minusf)@this).New(a, b) - ); /// - protected override Entity InnerSimplify() => - ExpandOnTwoArguments(Subtrahend.InnerSimplified, Minuend.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnTwoArguments(Subtrahend, Minuend, (augend, addend) => (augend, addend) switch { + (Complex a, Complex b) when !isExact => a - b, (var n1, Integer(0)) => n1, - (Integer(0), var n2) => (-n2), - (Interval inter, var n2) when n2 is not Set => inter.New((inter.Left - n2).InnerSimplified, (inter.Right - n2).InnerSimplified), - (var n2, Interval inter) when n2 is not Set => inter.New((n2 - inter.Left).InnerSimplified, (n2 - inter.Right).InnerSimplified), + (Integer(0), var n2) => -n2, + (Interval inter, var n2) when n2 is not Set => inter.New((inter.Left - n2).InnerSimplified(isExact), (inter.Right - n2).InnerSimplified(isExact)), + (var n2, Interval inter) when n2 is not Set => inter.New((n2 - inter.Left).InnerSimplified(isExact), (n2 - inter.Right).InnerSimplified(isExact)), _ => null }, - (@this, a, b) => ((Minusf)@this).New(a, b), - true); + (@this, a, b) => ((Minusf)@this).New(a, b), isExact); } public partial record Mulf { private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() => ExpandOnTwoArguments(Multiplier.Evaled, Multiplicand.Evaled, - (a, b) => (a, b) switch - { - (Matrix m1, Matrix m2) when m1.ColumnCount == m2.RowCount => (m1 * m2).Evaled, - (Matrix m1, Matrix m2) => a * b, - (Matrix m, Integer(0)) => m.With((_, _, _) => 0), - (Integer(0), Matrix m) => m.With((_, _, _) => 0), - (Complex n1, Complex n2) => n1 * n2, - ({ DomainCondition: var condition }, Integer(0)) => Integer.Zero.WithCondition(condition), - (Integer(0), { DomainCondition: var condition }) => Integer.Zero.WithCondition(condition), - (var n1, Integer(1)) => n1, - (Integer(1), var n2) => n2, - _ => null - }, - (@this, a, b) => ((Mulf)@this).New(a, b) - ); - /// - protected override Entity InnerSimplify() => - ExpandOnTwoArguments(Multiplier.InnerSimplified, Multiplicand.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnTwoArguments(Multiplier, Multiplicand, (a, b) => (a, b) switch { - (Matrix m1, Matrix m2) when m1.ColumnCount == m2.RowCount => (m1 * m2).InnerSimplified, + (Matrix m1, Matrix m2) when m1.ColumnCount == m2.RowCount => (m1 * m2).InnerSimplified(isExact), (Matrix m1, Matrix m2) => a * b, - (Integer minusOne, Mulf(var minusOne1, var any1)) when minusOne == Integer.MinusOne && minusOne1 == Integer.MinusOne => any1, + (Integer(-1), Mulf(Integer(-1), var any1)) => any1, (Matrix m, Integer(0)) => m.With((_, _, _) => 0), (Integer(0), Matrix m) => m.With((_, _, _) => 0), - ({ DomainCondition: var condition }, Integer(0)) => Integer.Zero.WithCondition(condition), - (Integer(0), { DomainCondition: var condition }) => Integer.Zero.WithCondition(condition), + (Complex n1, Complex n2) when !isExact => n1 * n2, + ({ DomainCondition: var condition }, Integer(0)) => Integer.Zero.Provided(condition), + (Integer(0), { DomainCondition: var condition }) => Integer.Zero.Provided(condition), (var n1, Integer(1)) => n1, (Integer(1), var n2) => n2, - (var n1, var n2) when n1 == n2 => new Powf(n1, 2).InnerSimplified, + (var n1, var n2) when n1 == n2 => new Powf(n1, 2).InnerSimplified(isExact), _ => null }, - (@this, a, b) => ((Mulf)@this).New(a, b), - true); + (@this, a, b) => ((Mulf)@this).New(a, b), isExact); } public partial record Divf { // Division is undefined when the divisor equals zero private protected override Entity IntrinsicCondition => !Divisor.Equalizes(0); + /// - protected override Entity InnerEval() => - ExpandOnTwoArguments(Dividend.Evaled, Divisor.Evaled, - (a, b) => (a, b) switch - { - (Complex n1, Complex n2) => n1 / n2, - (Integer(0), var n0) => n0.Evaled is Complex { IsZero: false } ? 0 : new Providedf(0, new Notf(new Equalsf(n0, 0))).Evaled, - (_, Integer(0)) => Real.NaN, - _ => null - }, - (@this, a, b) => ((Divf)@this).New(a, b) - ); - /// - protected override Entity InnerSimplify() => - ExpandOnTwoArguments(Dividend.InnerSimplified, Divisor.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnTwoArguments(Dividend, Divisor, (a, b) => (a, b) switch { - (Integer(0), var n0) => n0.Evaled is Complex { IsZero: false } ? 0 : new Providedf(0, new Notf(new Equalsf(n0, 0))).InnerSimplified, + (Integer(0), var n0) => + n0.Evaled is Complex c + ? c.IsZero ? MathS.NaN : 0 + : new Providedf(0, new Notf(new Equalsf(n0, 0))), + (_, Integer(0)) => Real.NaN, + (Complex n1, Complex n2) when !isExact => n1 / n2, (var n1, Integer(1)) => n1, _ => null }, - (@this, a, b) => ((Divf)@this).New(a, b), - true, - allowNaNAsExact: false); + (@this, a, b) => ((Divf)@this).New(a, b), isExact); } public partial record Powf { @@ -175,38 +122,34 @@ private static bool TryPower(Matrix m, int exp, out Entity res) } } - /// - protected override Entity InnerEval() => - ExpandOnTwoArguments(Base.Evaled, Exponent.Evaled, - (a, b) => (a, b) switch - { - (Matrix m, Integer(var exp)) when exp is { } expNotNull && TryPower(m, expNotNull, out var res) => res.Evaled, - (Complex n1, Complex n2) => Number.Pow(n1, n2), - (Integer(1), { DomainCondition: var condition }) => Integer.One.WithCondition(condition), - (Integer(0), var x) => new Providedf(0, x > 0).Evaled, - (var n1, Integer(-1)) => (1 / n1).Evaled, - (var x, Integer(0)) => new Providedf(1, !x.Equalizes(0)).Evaled, - (var n1, Integer(1)) => n1, - _ => null - }, - (@this, a, b) => ((Powf)@this).New(a, b) - ); + // Re(x) = x/2 * (1 + 1/sgn(x)^2) + internal static Entity Re(Entity x) => x / 2 * (1 + 1 / new Signumf(x).Pow(2)); /// - protected override Entity InnerSimplify() => - ExpandOnTwoArguments(Base.InnerSimplified, Exponent.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnTwoArguments(Base, Exponent, (a, b) => (a, b) switch { - (Matrix m, Integer(var exp)) when exp is { } expNotNull && TryPower(m, expNotNull, out var res) => res.InnerSimplified, - (Integer(1), { DomainCondition: var condition }) => Integer.One.WithCondition(condition), - (Integer(0), var x) => new Providedf(0, x > 0).InnerSimplified, - (var n1, Integer(-1)) => (1 / n1).InnerSimplified, - (var x, Integer(0)) => new Providedf(1, !x.Equalizes(0)).InnerSimplified, + (Matrix m, Integer(var exp)) when exp is { } expNotNull && TryPower(m, expNotNull, out var res) => res.InnerSimplified(isExact), + (Integer(0), var x) => + (isExact ? x.Evaled : x) is Complex c + ? c.RealPart.IsPositive + ? 0 + : MathS.NaN + : new Providedf(0, Re(x) > 0), + (Complex n1, Complex n2) when !isExact => Number.Pow(n1, n2), // returns 1 for 0^0 and 0 for 0^(negative real part), we need to handle these cases above + (Integer(1), { DomainCondition: var condition }) => Integer.One.Provided(condition), + (var n1, Integer(-1)) => (1 / n1).InnerSimplified(isExact), + (var x, Integer(0)) => + x.Evaled is Complex c + ? c.IsZero + ? throw new AngouriBugException("Should have already been handled by the above case") + : 1 + : new Providedf(1, !x.Equalizes(0)), (var n1, Integer(1)) => n1, _ => null }, - (@this, a, b) => ((Powf)@this).New(a, b), - true); + (@this, a, b) => ((Powf)@this).New(a, b), isExact); } public partial record Logf @@ -219,27 +162,16 @@ public partial record Logf Base > 0 & !Base.Equalizes(1) & Antilogarithm > 0; /// - protected override Entity InnerEval() => - ExpandOnTwoArguments(Base.Evaled, Antilogarithm.Evaled, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnTwoArguments(Base, Antilogarithm, (a, b) => (a, b) switch { - (Complex n1, Complex n2) => Number.Log(n1, n2), + (Complex n1, Complex n2) when !isExact => Number.Log(n1, n2), + ({ DomainCondition: var condition }, Integer(0)) => Real.NegativeInfinity.Provided(condition), + ({ DomainCondition: var condition }, Integer(1)) => Integer.Zero.Provided(condition), _ => null }, - (@this, a, b) => ((Logf)@this).New(a, b) - ); - - /// - protected override Entity InnerSimplify() => - ExpandOnTwoArguments(Base.InnerSimplified, Antilogarithm.InnerSimplified, - (a, b) => (a, b) switch - { - ({ DomainCondition: var condition }, Integer(0)) => Real.NegativeInfinity.WithCondition(condition), - ({ DomainCondition: var condition }, Integer(1)) => Integer.Zero.WithCondition(condition), - _ => null - }, - (@this, a, b) => ((Logf)@this).New(a, b), - true); + (@this, a, b) => ((Logf)@this).New(a, b), isExact); } public partial record Factorialf @@ -249,22 +181,13 @@ public partial record Factorialf // The gamma function has poles at negative integers, so factorial is undefined there private protected override Entity IntrinsicCondition => Argument.In(MathS.Sets.R) & (Argument >= 0 | !Argument.In(MathS.Sets.Z)); - - /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.Evaled, - a => a switch - { - Complex n => Number.Factorial(n), - _ => null - }, - (@this, a) => ((Factorialf)@this).New(a) - ); + /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnOneArgument(Argument, a => a switch { + Complex c when !isExact => Number.Factorial(c), Rational({ Numerator: var num, Denominator: var den }) when den.Equals(2) && (num + 1) / 2 is var en => ( en > 0 // (+n - 1/2)! = (2n-1)!/(2^(2n-1)(n-1)!)*sqrt(pi) @@ -275,8 +198,7 @@ protected override Entity InnerSimplify() => ) * MathS.Sqrt(MathS.pi), _ => null }, - (@this, a) => ((Factorialf)@this).New(a) - , true); + (@this, a) => ((Factorialf)@this).New(a), isExact); } public partial record Signumf @@ -284,28 +206,17 @@ public partial record Signumf // Signum is defined everywhere in the complex plane // sgn(0) = 0, sgn(z) = z/|z| for z ≠ 0 private protected override Entity IntrinsicCondition => Boolean.True; - - /// - protected override Entity InnerEval() - => ExpandOnOneArgument(Argument.Evaled, - a => a switch - { - Complex n => Number.Signum(n), - _ => null - }, - (@this, a) => ((Signumf)@this).New(a) - ); - // TODO: probably we can simplify it further /// - protected override Entity InnerSimplify() - => ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnOneArgument(Argument, a => a switch { + Real n => n.EDecimal.Sign, + Complex n when !isExact => Number.Signum(n), _ => null }, - (@this, a) => ((Signumf)@this).New(a) - , true); + (@this, a) => ((Signumf)@this).New(a), isExact); } public partial record Absf @@ -313,30 +224,17 @@ public partial record Absf // Absolute value is defined everywhere in the complex plane // For complex z, |z| = sqrt(Re(z)^2 + Im(z)^2) private protected override Entity IntrinsicCondition => Boolean.True; - - /// - protected override Entity InnerEval() - => ExpandOnOneArgument(Argument.Evaled, - a => a switch - { - Matrix m when m.IsVector => Sumf.Sum(m.Select(c => c.Pow(2))).Pow(0.5).Evaled, - Complex n => Number.Abs(n), - _ => null - }, - (@this, a) => ((Absf)@this).New(a) - ); - // TODO: probably we can simplify it further /// - protected override Entity InnerSimplify() - => ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnOneArgument(Argument, a => a switch { Matrix m when m.IsVector => Sumf.Sum(m.Select(c => c.Pow(2))).Pow(0.5).InnerSimplified, + Complex n when !isExact => Number.Abs(n), _ => null }, - (@this, a) => ((Absf)@this).New(a) - , true); + (@this, a) => ((Absf)@this).New(a), isExact); } } } diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Calculus.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Calculus.Classes.cs index 8f0b27f14..8cbf8adbd 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Calculus.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Calculus.Classes.cs @@ -16,54 +16,29 @@ public partial record Derivativef // The derivative operator is always defined symbolically, even though // the resulting expression may be undefined at certain points. private protected override Entity IntrinsicCondition => Boolean.True; - - /// - protected override Entity InnerEval() => - ExpandOnTwoAndTArguments(Expression.Evaled, Var.Evaled, Iterations, - (a, b, c) => (a, b, c) switch - { - (var expr, _, 0) => expr, - (_, _, int.MinValue) => null, - (_, _, < 0) => new Integralf(a, b, -c).Evaled, - // TODO: should we call InnerSimplified here? - (var expr, Variable var, int asInt) - when expr.Differentiate(var, asInt) is var res and not Derivativef - => res.Evaled, - (var expr, Variable var, int asInt) => null, - (Application, _, _) => null, - (var expr, Entity otherExpr, int asInt) - when Variable.CreateTemp(otherExpr.Vars) is var tempVar - && expr.Substitute(otherExpr, tempVar) is var tempSubstituted - && tempSubstituted.Differentiate(tempVar) is var res and not Derivativef - => res.Substitute(tempVar, otherExpr).Evaled, - _ => null - }, - (@this, a, b, _) => ((Derivativef)@this).New(a, b) - ); /// - protected override Entity InnerSimplify() => - ExpandOnTwoAndTArguments(Expression.Evaled, Var.Evaled, Iterations, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnTwoAndTArguments(Expression, Var, Iterations, (a, b, c) => (a, b, c) switch { (var expr, _, 0) => expr, (_, _, int.MinValue) => null, - (_, _, < 0) => new Integralf(a, b, -c).InnerSimplified, - // TODO: should we call InnerSimlified here? + (_, _, < 0) => new Integralf(a, b, -c).InnerSimplified(isExact), + // TODO: should we call InnerSimplified here? (var expr, Variable var, int asInt) when expr.Differentiate(var, asInt) is var res and not Derivativef - => res.InnerSimplified, + => res.InnerSimplified(isExact), (var expr, Variable var, int asInt) => null, (Application, _, _) => null, (var expr, Entity otherExpr, int asInt) when Variable.CreateTemp(otherExpr.Vars) is var tempVar && expr.Substitute(otherExpr, tempVar) is var tempSubstituted && tempSubstituted.Differentiate(tempVar) is var res and not Derivativef - => res.Substitute(tempVar, otherExpr).InnerSimplified, + => res.Substitute(tempVar, otherExpr).InnerSimplified(isExact), _ => null }, - (@this, a, b, _) => ((Derivativef)@this).New(a, b) - ); + (@this, a, b, _) => ((Derivativef)@this).New(a, b), isExact); } public partial record Integralf @@ -83,39 +58,21 @@ private Entity SequentialIntegrating(Entity expr, Variable var, int iterations) } /// - protected override Entity InnerEval() => - ExpandOnTwoAndTArguments(Expression.Evaled, Var.Evaled, Iterations, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnTwoAndTArguments(Expression, Var, Iterations, (a, b, c) => (a, b, c) switch { (var expr, _, 0) => expr, (_, _, int.MinValue) => null, - (_, _, < 0) => new Derivativef(a, b, -c).Evaled, - (var expr, Variable var, int asInt) - when SequentialIntegrating(expr, var, asInt) is var res and not Integralf - && !res.Nodes.Any(n => n is Integralf) - => res.Evaled, - _ => null - }, - (@this, a, b, _) => ((Integralf)@this).New(a, b) - ); - - /// - protected override Entity InnerSimplify() => - ExpandOnTwoAndTArguments(Expression.InnerSimplified, Var, Iterations, - (a, b, c) => (a, b, c) switch - { - (var expr, _, 0) => expr, - (_, _, int.MinValue) => null, - (_, _, < 0 and not int.MinValue) => new Derivativef(a, b, -c).InnerSimplified, + (_, _, < 0 and not int.MinValue) => new Derivativef(a, b, -c).InnerSimplified(isExact), // TODO: should we apply InnerSimplified? (var expr, Variable var, int asInt) when SequentialIntegrating(expr, var, asInt) is var res and not Integralf && !res.Nodes.Any(n => n is Integralf) - => res.InnerSimplified, + => res.InnerSimplified(isExact), _ => null }, - (@this, a, b, _) => ((Integralf)@this).New(a, b) - ); + (@this, a, b, _) => ((Integralf)@this).New(a, b), isExact); } @@ -127,31 +84,15 @@ public partial record Limitf private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() => + protected override Entity InnerSimplify(bool isExact) => ExpandOnTwoAndTArguments( - Expression.Evaled, Destination.Evaled, (v: Var, ap: ApproachFrom), + Expression, Destination, (v: Var, ap: ApproachFrom), (expr, dest, vap) => vap.v switch { - Variable v when expr.Limit(v, dest, vap.ap) is var res and not Limitf - => res.Evaled, + Variable v when expr.Limit(v, dest, vap.ap) is var res and not Limitf => res.InnerSimplified(isExact), _ => null }, - (@this, expr, dest, vap) => ((Limitf)@this).New(expr, vap.v, dest, vap.ap) - ); - - /// - protected override Entity InnerSimplify() => - ExpandOnTwoAndTArguments( - Expression.InnerSimplified, Destination.InnerSimplified, (v: Var, ap: ApproachFrom), - (expr, dest, vap) => vap.v switch - { - Variable v when expr.Limit(v, dest, vap.ap) is var res and not Limitf - => res.InnerSimplified, - _ => null - }, - (@this, expr, dest, vap) => ((Limitf)@this).New(expr, vap.v, dest, vap.ap) - ); - + (@this, expr, dest, vap) => ((Limitf)@this).New(expr, vap.v, dest, vap.ap), isExact); } } } diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Trigonometry.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Trigonometry.Classes.cs index 0e2088aac..fd293d59d 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Trigonometry.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Continuous/Evaluation.Continuous.Trigonometry.Classes.cs @@ -5,6 +5,8 @@ // Website: https://am.angouri.org. // +using Antlr4.Runtime.Misc; + namespace AngouriMath { internal static class InnerEvalZeroedSinCosConditions @@ -40,308 +42,206 @@ public partial record Sinf { private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.Evaled, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnOneArgument(Argument, a => a switch { - Complex n => Number.Sin(n), + Complex n when !isExact => Number.Sin(n), + { Evaled: Complex n } arg when isExact && TrigonometryTableValues.PullSin(n, out var res) => res, _ => null }, - (@this, a) => ((Sinf)@this).New(a) - ); - - /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, - a => a switch - { - { Evaled: Complex n } arg when TrigonometryTableValues.PullSin(n, out var res) => res, - _ => null - }, - (@this, a) => ((Sinf)@this).New(a), - true); + (@this, a) => ((Sinf)@this).New(a), isExact); } public partial record Cosf { private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.Evaled, - a => a switch - { - Complex n => Number.Cos(n), - _ => null - }, - (@this, a) => ((Cosf)@this).New(a) - ); - - /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnOneArgument(Argument, a => a switch { - { Evaled: Complex n } when TrigonometryTableValues.PullCos(n, out var res) => res, + Complex n when !isExact => Number.Cos(n), + { Evaled: Complex n } when isExact && TrigonometryTableValues.PullCos(n, out var res) => res, _ => null }, - (@this, a) => ((Cosf)@this).New(a), - true); + (@this, a) => ((Cosf)@this).New(a), isExact); } public partial record Secantf { private protected override Entity IntrinsicCondition => !MathS.Cos(Argument).Equalizes(0); // Sec(x) = 1/cos(x) is undefined when cos(x) = 0 /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.InnerSimplified, - a => a switch - { - var arg when InnerEvalZeroedSinCosConditions.IsCosDefinitelyZero(arg) => MathS.NaN, - var n => (n.Evaled as Complex)?.Pipe(Secant) - }, - (@this, a) => ((Secantf)@this).New(a) - ); - - /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, - a => a switch - { - { Evaled: Complex n } when TrigonometryTableValues.PullCos(n, out var res) => (1 / res).InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + InnerEvalZeroedSinCosConditions.IsCosDefinitelyZero(Argument.InnerSimplified) ? MathS.NaN + : ExpandOnOneArgument(Argument, a => + a switch + { + { Evaled: Complex n } => + isExact + ? TrigonometryTableValues.PullCos(n, out var res) + ? (1 / res).InnerSimplified + : null + : Secant(n), _ => null }, - (@this, a) => ((Secantf)@this).New(a), - true); + (@this, a) => ((Secantf)@this).New(a), isExact); } public partial record Cosecantf { private protected override Entity IntrinsicCondition => !MathS.Sin(Argument).Equalizes(0); // Csc(x) = 1/sin(x) is undefined when sin(x) = 0 /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + InnerEvalZeroedSinCosConditions.IsSinDefinitelyZero(Argument.InnerSimplified) ? MathS.NaN + : ExpandOnOneArgument(Argument, a => a switch { - var arg when InnerEvalZeroedSinCosConditions.IsSinDefinitelyZero(arg) => MathS.NaN, - var n => (n.Evaled as Complex)?.Pipe(Cosecant) - }, - (@this, a) => ((Cosecantf)@this).New(a) - ); - - /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, - a => a switch - { - { Evaled: Complex n } when TrigonometryTableValues.PullSin(n, out var res) => (1 / res).InnerSimplified, + { Evaled: Complex n } => + isExact + ? TrigonometryTableValues.PullSin(n, out var res) + ? (1 / res).InnerSimplified + : null + : Cosecant(n), _ => null }, - (@this, a) => ((Cosecantf)@this).New(a) - , true); + (@this, a) => ((Cosecantf)@this).New(a), isExact); } public partial record Arcsecantf { - private protected override Entity IntrinsicCondition => MathS.Abs(Argument) >= 1; // Arcsec is defined for |x| >= 1 - /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.Evaled, - a => a switch - { - Complex n => Number.Arcsecant(n), - _ => null - }, - (@this, a) => ((Arcsecantf)@this).New(a) - ); - + private protected override Entity IntrinsicCondition => + Codomain < Domain.Complex + ? MathS.Abs(Argument) >= 1 // Arcsec is defined for |x| >= 1 for reals + : !Argument.Equalizes(0); // Arcsec is undefined at 0 in complex /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnOneArgument(Argument, a => a switch { + Complex n when !isExact => Arcsecant(n), _ => null }, - (@this, a) => ((Arcsecantf)@this).New(a), - true); + (@this, a) => ((Arcsecantf)@this).New(a), isExact); } public partial record Arccosecantf { - private protected override Entity IntrinsicCondition => MathS.Abs(Argument) >= 1; // Arccsc is defined for |x| >= 1 - /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.Evaled, - a => a switch - { - Complex n => Number.Arccosecant(n), - _ => null - }, - (@this, a) => ((Arccosecantf)@this).New(a) - ); - + private protected override Entity IntrinsicCondition => + Codomain < Domain.Complex + ? MathS.Abs(Argument) >= 1 // Arccsc is defined for |x| >= 1 for reals + : !Argument.Equalizes(0); // Arccsc is undefined at 0 in complex /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnOneArgument(Argument, a => a switch { + Complex n when !isExact => Number.Arccosecant(n), _ => null }, - (@this, a) => ((Arccosecantf)@this).New(a), - true); + (@this, a) => ((Arccosecantf)@this).New(a), isExact); } public partial record Tanf { - private protected override Entity IntrinsicCondition => !MathS.Cos(Argument).Equalizes(0); // Tan(x) = sin(x)/cos(x) is undefined when cos(x) = 0 - /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.InnerSimplified, - a => a switch - { - var arg when InnerEvalZeroedSinCosConditions.IsCosDefinitelyZero(arg) => MathS.NaN, - var n => (n.Evaled as Complex)?.Pipe(Number.Tan) - }, - (@this, a) => ((Tanf)@this).New(a) - ); + private protected override Entity IntrinsicCondition => + !MathS.Cos(Argument).Equalizes(0); // Tan(x) = sin(x)/cos(x) is undefined when cos(x) = 0 /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + InnerEvalZeroedSinCosConditions.IsCosDefinitelyZero(Argument.InnerSimplified) ? MathS.NaN + : ExpandOnOneArgument(Argument, a => a switch { - { Evaled: Complex n } when TrigonometryTableValues.PullTan(n, out var res) => res, + { Evaled: Complex n } => + isExact + ? TrigonometryTableValues.PullTan(n, out var res) ? res : null + : Number.Tan(n), _ => null }, - (@this, a) => ((Tanf)@this).New(a) - , true); + (@this, a) => ((Tanf)@this).New(a), isExact); } public partial record Cotanf { private protected override Entity IntrinsicCondition => !MathS.Sin(Argument).Equalizes(0); // Cotan(x) = cos(x)/sin(x) is undefined when sin(x) = 0 /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.InnerSimplified, - a => a switch - { - var arg when InnerEvalZeroedSinCosConditions.IsSinDefinitelyZero(arg) => MathS.NaN, - var n => (n.Evaled as Complex)?.Pipe(Number.Cotan) - }, - (@this, a) => ((Cotanf)@this).New(a) - ); - - /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + InnerEvalZeroedSinCosConditions.IsSinDefinitelyZero(Argument.InnerSimplified) ? MathS.NaN + : ExpandOnOneArgument(Argument, a => a switch { - { Evaled: Complex n } when TrigonometryTableValues.PullTan(n, out var res) => (1 / res).InnerSimplified, + { Evaled: Complex n } => + isExact + ? TrigonometryTableValues.PullTan(n, out var res) ? (1 / res).InnerSimplified : null + : Number.Cotan(n), _ => null }, - (@this, a) => ((Cotanf)@this).New(a) - , true); + (@this, a) => ((Cotanf)@this).New(a), isExact); } public partial record Arcsinf { // Arcsin is defined for |x| <= 1 if we restrict to reals, but everywhere in complex - private protected override Entity IntrinsicCondition => Boolean.True; - - /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.Evaled, - a => a switch - { - Complex n => Number.Arcsin(n), - _ => null - }, - (@this, a) => ((Arcsinf)@this).New(a) - ); + private protected override Entity IntrinsicCondition => + Codomain < Domain.Complex ? MathS.Abs(Argument) <= 1 : Boolean.True; + /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnOneArgument(Argument, a => a switch { + Complex n when !isExact => Number.Arcsin(n), _ => null }, - (@this, a) => ((Arcsinf)@this).New(a), - true); + (@this, a) => ((Arcsinf)@this).New(a), isExact); } public partial record Arccosf { // Arccos is defined for |x| <= 1 if we restrict to reals, but everywhere in complex - private protected override Entity IntrinsicCondition => Boolean.True; - - /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.Evaled, - a => a switch - { - Complex n => Number.Arccos(n), - _ => null - }, - (@this, a) => ((Arccosf)@this).New(a) - ); + private protected override Entity IntrinsicCondition => + Codomain < Domain.Complex ? MathS.Abs(Argument) <= 1 : Boolean.True; + /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnOneArgument(Argument, a => a switch { + Complex n when !isExact => Number.Arccos(n), _ => null }, - (@this, a) => ((Arccosf)@this).New(a), - true); + (@this, a) => ((Arccosf)@this).New(a), isExact); } public partial record Arctanf { private protected override Entity IntrinsicCondition => Boolean.True; - - /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.Evaled, - a => a switch - { - Complex n => Number.Arctan(n), - _ => null - }, - (@this, a) => ((Arctanf)@this).New(a) - ); + /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnOneArgument(Argument, a => a switch { + Complex n when !isExact => Number.Arctan(n), _ => null }, - (@this, a) => ((Arctanf)@this).New(a) - , true); + (@this, a) => ((Arctanf)@this).New(a), isExact); } public partial record Arccotanf { private protected override Entity IntrinsicCondition => Boolean.True; - - /// - protected override Entity InnerEval() => - ExpandOnOneArgument(Argument.Evaled, - a => a switch - { - Complex n => Number.Arccotan(n), - _ => null - }, - (@this, a) => ((Arccotanf)@this).New(a) - ); + /// - protected override Entity InnerSimplify() => - ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) => + ExpandOnOneArgument(Argument, a => a switch { + Complex n when !isExact => Number.Arccotan(n), _ => null }, - (@this, a) => ((Arccotanf)@this).New(a) - , true); + (@this, a) => ((Arccotanf)@this).New(a), isExact); } } } diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Definition.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Definition.cs index b1d0302b0..bd70710bb 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Definition.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Definition.cs @@ -9,6 +9,11 @@ namespace AngouriMath { + internal static class EntityEvaluationExtension { + /// Returns either or based on + internal static Entity InnerSimplified(this Entity @this, bool isExact) => isExact ? @this.InnerSimplified : @this.Evaled; + } + partial record Entity { /// @@ -64,14 +69,77 @@ partial record Entity /// independent of any restrictions on the input variables themselves. /// private protected abstract Entity IntrinsicCondition { get; } - internal Entity WithCondition(Entity condition) => condition == Boolean.True ? this : new Providedf(this, condition); + /// /// This should NOT be called inside itself /// - protected abstract Entity InnerSimplify(); + protected abstract Entity InnerSimplify(bool isExact); + + private Entity InnerSimplifyWithCheck(bool isExact) + { + var innerSimplified = InnerSimplify(isExact); + if (innerSimplified.DirectChildren.Any(c => c == MathS.NaN)) + return MathS.NaN; + if (DomainsFunctional.FitsDomainOrNonNumeric(innerSimplified, Codomain)) + return innerSimplified; + else + return MathS.NaN; + } + + /// + /// Represents the evaluated value of the given expression, allowing imprecise values unlike . + /// Unlike the result of and + /// + /// this is not constrained by any type. + /// + /// It only performs an active operation in the first call, + /// next time it is free to call it in terms of CPU usage. For + /// consistency's sake, consider the call of this property + /// as free as the addressing of a field. + /// + /// + /// + /// using System; + /// using static AngouriMath.MathS; + /// + /// var (x, y) = Var("x", "y"); + /// var expr1 = x + y; + /// Console.WriteLine(expr1); + /// Console.WriteLine(expr1.Evaled); + /// Console.WriteLine(expr1.Evaled.GetType()); + /// Console.WriteLine("-----------------------------"); + /// var expr2 = 5 + x * i; + /// Console.WriteLine(expr2); + /// Console.WriteLine(expr2.Evaled); + /// Console.WriteLine(expr2.Substitute(x, 3).Evaled); + /// Console.WriteLine(expr2.Substitute(x, 3).Evaled.GetType()); + /// Console.WriteLine("-----------------------------"); + /// var expr3 = GreaterThan(5, 3); + /// Console.WriteLine(expr3); + /// Console.WriteLine(expr3.Evaled); + /// Console.WriteLine(expr3.Evaled.GetType()); + /// + /// Prints + /// + /// x + y + /// x + y + /// AngouriMath.Entity+Sumf + /// ----------------------------- + /// 5 + x * i + /// 5 + x * i + /// 5 + 3i + /// AngouriMath.Entity+Number+Complex + /// ----------------------------- + /// 5 > 3 + /// True + /// AngouriMath.Entity+Boolean + /// + /// + public Entity Evaled => evaled.GetValue(static @this => @this.InnerSimplifyWithCheck(false), this); + private LazyPropertyA evaled; /// - /// This is the result of naive simplifications. In other + /// This is the result of naive simplifications, but not creating imprecise values unlike . In other /// symbolic algebra systems it is called "Automatic simplification". /// It only performs an active operation in the first call, /// next time it is free to call it in terms of CPU usage. For @@ -96,40 +164,9 @@ partial record Entity /// 1 /// /// - public Entity InnerSimplified => innerSimplified.GetValue(static @this => @this.InnerSimplifyWithCheck(), this); + public Entity InnerSimplified => innerSimplified.GetValue(static @this => @this.InnerSimplifyWithCheck(true), this); private LazyPropertyA innerSimplified; - - private Entity InnerActionWithCheck(IEnumerable directChildren, Entity innerSimplifiedOrEvaled, bool returnThisIfNaN) - { - if (innerSimplifiedOrEvaled.DirectChildren.Any(c => c == MathS.NaN)) - return MathS.NaN; - if (DomainsFunctional.FitsDomainOrNonNumeric(innerSimplifiedOrEvaled, Codomain)) - return innerSimplifiedOrEvaled; - if (returnThisIfNaN) - return this; - else - return MathS.NaN; - } - - /// - /// Make sure you call this function inside of - /// - internal Entity InnerSimplifyWithCheck() - => InnerActionWithCheck(DirectChildren.Select(c => c.InnerSimplified), InnerSimplify(), true); - - /// - /// This should NOT be called inside itself - /// - protected abstract Entity InnerEval(); - - /// - /// Make sure you call this function inside of - /// - protected Entity InnerEvalWithCheck() - => InnerActionWithCheck(DirectChildren.Select(c => c.Evaled), InnerEval(), false); - - /// /// Expands an equation trying to eliminate all the parentheses ( e. g. 2 * (x + 3) = 2 * x + 2 * 3 ) /// @@ -289,58 +326,6 @@ public Entity Factorize(int level = 2) => level <= 1 /// public IEnumerable Alternate(int level) => Simplificator.Alternate(this, level); - /// - /// Represents the evaluated value of the given expression - /// Unlike the result of and - /// - /// this is not constrained by any type. - /// - /// It only performs an active operation in the first call, - /// next time it is free to call it in terms of CPU usage. For - /// consistency's sake, consider the call of this property - /// as free as the addressing of a field. - /// - /// - /// - /// using System; - /// using static AngouriMath.MathS; - /// - /// var (x, y) = Var("x", "y"); - /// var expr1 = x + y; - /// Console.WriteLine(expr1); - /// Console.WriteLine(expr1.Evaled); - /// Console.WriteLine(expr1.Evaled.GetType()); - /// Console.WriteLine("-----------------------------"); - /// var expr2 = 5 + x * i; - /// Console.WriteLine(expr2); - /// Console.WriteLine(expr2.Evaled); - /// Console.WriteLine(expr2.Substitute(x, 3).Evaled); - /// Console.WriteLine(expr2.Substitute(x, 3).Evaled.GetType()); - /// Console.WriteLine("-----------------------------"); - /// var expr3 = GreaterThan(5, 3); - /// Console.WriteLine(expr3); - /// Console.WriteLine(expr3.Evaled); - /// Console.WriteLine(expr3.Evaled.GetType()); - /// - /// Prints - /// - /// x + y - /// x + y - /// AngouriMath.Entity+Sumf - /// ----------------------------- - /// 5 + x * i - /// 5 + x * i - /// 5 + 3i - /// AngouriMath.Entity+Number+Complex - /// ----------------------------- - /// 5 > 3 - /// True - /// AngouriMath.Entity+Boolean - /// - /// - public Entity Evaled => evaled.GetValue(static @this => @this.InnerEvalWithCheck(), this); - private LazyPropertyA evaled; - /// /// Determines whether a given element can be unambiguously used as a number or boolean /// diff --git a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Discrete/Evaluation.Discrete.Classes.cs b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Discrete/Evaluation.Discrete.Classes.cs index 398ffa970..03feb6904 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Discrete/Evaluation.Discrete.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Discrete/Evaluation.Discrete.Classes.cs @@ -5,7 +5,9 @@ // Website: https://am.angouri.org. // +using System.Xml.Linq; using static AngouriMath.Entity.Boolean; +using static AngouriMath.Entity.Set; namespace AngouriMath { @@ -17,10 +19,7 @@ partial record Boolean private protected override Entity IntrinsicCondition => True; /// - protected override Entity InnerEval() => this; - - /// - protected override Entity InnerSimplify() => this; + protected override Entity InnerSimplify(bool isExact) => this; } partial record Notf @@ -29,259 +28,99 @@ partial record Notf private protected override Entity IntrinsicCondition => True; /// - protected override Entity InnerEval() - => ExpandOnOneArgument(Argument.Evaled, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnOneArgument(Argument, a => a switch { - Boolean b => !(bool)b, + Boolean(var b) => !b, _ => null }, - (@this, a) => ((Notf)@this).New(a) - ); - /// - protected override Entity InnerSimplify() - => Evaled is Boolean b ? b : New(Argument.InnerSimplified); + (@this, a) => ((Notf)@this).New(a), isExact); } partial record Andf { // Logical AND is always defined for any inputs private protected override Entity IntrinsicCondition => True; - - private static bool GoodResult(Entity left, Entity right, Entity leftEvaled, Entity rightEvaled, out Entity res) - { - if (leftEvaled is Boolean leftBool && rightEvaled is Boolean rightBool) - { - res = (bool)leftBool && (bool)rightBool; - return true; - } - else if (leftEvaled == False || rightEvaled == False) - { - res = False; - return true; - } - else if (leftEvaled == True) - { - res = right; - return true; - } - else if (rightEvaled == True) - { - res = left; - return true; - } - else - { - res = False; - return false; - } - } - - /// - protected override Entity InnerEval() - => ExpandOnTwoArguments(Left.Evaled, Right.Evaled, - (a, b) => (a, b) switch - { - (var left, var right) when GoodResult(left, right, left, right, out var res) => res, - _ => null - }, - (@this, a, b) => ((Andf)@this).New(a, b) - ); - /// - protected override Entity InnerSimplify() - => ExpandOnTwoArguments(Left.InnerSimplified, Right.InnerSimplified, - (a, b) => (a, b) switch + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Left, Right, + (left, right) => left == right ? left : (left.Evaled, right.Evaled) switch { - (var left, var right) when GoodResult(left, right, left.Evaled, right.Evaled, out var res) => res, + (Boolean(false), _) or (_, Boolean(false)) => False, + (Boolean(true), _) => right, + (_, Boolean(true)) => left, _ => null }, - (@this, a, b) => ((Andf)@this).New(a, b) - ); + (@this, a, b) => ((Andf)@this).New(a, b), isExact); } partial record Orf { // Logical OR is always defined for any inputs private protected override Entity IntrinsicCondition => True; - - private static bool GoodResult(Entity left, Entity right, Entity leftEvaled, Entity rightEvaled, out Entity res) - { - if (leftEvaled is Boolean leftBool && rightEvaled is Boolean rightBool) - { - res = (bool)leftBool || (bool)rightBool; - return true; - } - else if (leftEvaled == True || rightEvaled == True) - { - res = True; - return true; - } - else if (leftEvaled == False) - { - res = right; - return true; - } - else if (rightEvaled == False) - { - res = left; - return true; - } - else - { - res = False; - return false; - } - } - /// - protected override Entity InnerEval() - => ExpandOnTwoArguments(Left.Evaled, Right.Evaled, - (a, b) => (a, b) switch + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Left, Right, + (left, right) => (left.Evaled, right.Evaled) switch { - (var left, var right) when GoodResult(left, right, left, right, out var res) => res, + (Boolean(true), _) or (_, Boolean(true)) => True, + (Boolean(false), _) => right, + (_, Boolean(false)) => left, _ => null }, - (@this, a, b) => ((Orf)@this).New(a, b) - ); - - /// - protected override Entity InnerSimplify() - => ExpandOnTwoArguments(Left.InnerSimplified, Right.InnerSimplified, - (a, b) => (a, b) switch - { - (var left, var right) when GoodResult(left, right, left.Evaled, right.Evaled, out var res) => res, - _ => null - }, - (@this, a, b) => ((Orf)@this).New(a, b) - ); + (@this, a, b) => ((Orf)@this).New(a, b), isExact); } partial record Xorf { // Logical XOR is always defined for any inputs private protected override Entity IntrinsicCondition => True; - - private static bool GoodResult(Entity left, Entity right, Entity leftEvaled, Entity rightEvaled, out Entity res) - { - if (leftEvaled is Boolean leftBool && rightEvaled is Boolean rightBool) - { - res = (bool)leftBool ^ (bool)rightBool; - return true; - } - else if (leftEvaled is Boolean leftBoolOnly) - { - res = leftBoolOnly ? !right : right; - return true; - } - else if (rightEvaled is Boolean rightBoolOnly) - { - res = rightBoolOnly ? !left : left; - return true; - } - else - { - res = False; - return false; - } - } - - /// - protected override Entity InnerEval() - => ExpandOnTwoArguments(Left.Evaled, Right.Evaled, - (a, b) => (a, b) switch - { - (var left, var right) when GoodResult(left, right, left, right, out var res) => res, - _ => null - }, - (@this, a, b) => ((Xorf)@this).New(a, b) - ); - /// - protected override Entity InnerSimplify() - => ExpandOnTwoArguments(Left.InnerSimplified, Right.InnerSimplified, - (a, b) => (a, b) switch + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Left, Right, + (left, right) => (left.Evaled, right.Evaled) switch { - (var left, var right) when GoodResult(left, right, left.Evaled, right.Evaled, out var res) => res, + (Boolean(var leftBool), Boolean(var rightBool)) => leftBool ^ rightBool, + (Boolean(true), _) => !right, + (Boolean(false), _) => right, + (_, Boolean(true)) => !left, + (_, Boolean(false)) => left, _ => null }, - (@this, a, b) => ((Xorf)@this).New(a, b) - ); + (@this, a, b) => ((Xorf)@this).New(a, b), isExact); } partial record Impliesf { // Logical implication is always defined for any inputs private protected override Entity IntrinsicCondition => True; - - private static bool GoodResult(Entity left, Entity right, Entity leftEvaled, Entity rightEvaled, out Entity res) - { - if (leftEvaled is Boolean leftBool && rightEvaled is Boolean rightBool) - { - res = !(bool)leftBool || (bool)rightBool; - return true; - } - else if (leftEvaled == False || rightEvaled == True) - { - res = True; - return true; - } - else if (leftEvaled == True) - { - res = right; - return true; - } - else if (rightEvaled == False) - { - res = !left; - return true; - } - else - { - res = False; - return false; - } - } - /// - protected override Entity InnerEval() - => ExpandOnTwoArguments(Assumption.Evaled, Conclusion.Evaled, - (a, b) => (a, b) switch - { - (var left, var right) when GoodResult(left, right, left, right, out var res) => res, - _ => null - }, - (@this, a, b) => ((Impliesf)@this).New(a, b) - ); - - /// - protected override Entity InnerSimplify() - => ExpandOnTwoArguments(Assumption.InnerSimplified, Conclusion.InnerSimplified, - (a, b) => (a, b) switch + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Assumption, Conclusion, + (left, right) => (left.Evaled, right.Evaled) switch { - (var left, var right) when GoodResult(left, right, left.Evaled, right.Evaled, out var res) => res, + (Boolean(var leftBool), Boolean(var rightBool)) => !leftBool || rightBool, + (Boolean(false), _) => True, + (Boolean(true), _) => right, + (_, Boolean(true)) => True, + (_, Boolean(false)) => !left, _ => null }, - (@this, a, b) => ((Impliesf)@this).New(a, b) - ); + (@this, a, b) => ((Impliesf)@this).New(a, b), isExact); } partial record Equalsf { // Equality comparison is always defined for any inputs private protected override Entity IntrinsicCondition => True; - - /// - protected override Entity InnerEval() - => (Left.Evaled, Right.Evaled) switch - { - (var left, var right) when left == right => true, - (var left, var right) when left.IsConstant && right.IsConstant => left == right, - (var left, var right) => MathS.Equality(left, right) - }; /// - protected override Entity InnerSimplify() - => Evaled is Boolean b ? b : MathS.Equality(Left.InnerSimplified, Right.InnerSimplified); + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Left, Right, + (left, right) => left == right ? true + : left.IsConstant && right.IsConstant ? left.Evaled == right.Evaled + : null, + (@this, a, b) => ((Equalsf)@this).New(a, b), isExact); } partial record Greaterf @@ -290,31 +129,16 @@ partial record Greaterf // For non-real complex numbers, they evaluate to NaN. private protected override Entity IntrinsicCondition => Left.In(MathS.Sets.R) & Right.In(MathS.Sets.R); - /// - protected override Entity InnerEval() - => ExpandOnTwoArguments(Left.Evaled, Right.Evaled, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Left, Right, (a, b) => (a, b) switch { - (Real nan, _) when nan == MathS.NaN => MathS.NaN, - (_, Real nan) when nan == MathS.NaN => MathS.NaN, (Real reLeft, Real reRight) => reLeft > reRight, (Number numLeft, Number numRight) => MathS.NaN, _ => null }, - (@this, a, b) => ((Greaterf)@this).New(a, b) - ); - - /// - protected override Entity InnerSimplify() - => Evaled is Boolean b ? b : - ExpandOnTwoArguments(Left.InnerSimplified, Right.InnerSimplified, - (a, b) => (a, b) switch - { - _ => null - }, - (@this, a, b) => ((Greaterf)@this).New(a, b) - ); + (@this, a, b) => ((Greaterf)@this).New(a, b), isExact); } partial record GreaterOrEqualf @@ -323,31 +147,17 @@ partial record GreaterOrEqualf // For non-real complex numbers, they evaluate to NaN. private protected override Entity IntrinsicCondition => Left.In(MathS.Sets.R) & Right.In(MathS.Sets.R); - + /// - protected override Entity InnerEval() - => ExpandOnTwoArguments(Left.Evaled, Right.Evaled, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Left, Right, (a, b) => (a, b) switch { - (Real nan, _) when nan == MathS.NaN => MathS.NaN, - (_, Real nan) when nan == MathS.NaN => MathS.NaN, (Real reLeft, Real reRight) => reLeft >= reRight, (Number numLeft, Number numRight) => MathS.NaN, _ => null }, - (@this, a, b) => ((GreaterOrEqualf)@this).New(a, b) - ); - - /// - protected override Entity InnerSimplify() - => Evaled is Boolean b ? b : - ExpandOnTwoArguments(Left.InnerSimplified, Right.InnerSimplified, - (a, b) => (a, b) switch - { - _ => null - }, - (@this, a, b) => ((GreaterOrEqualf)@this).New(a, b) - ); + (@this, a, b) => ((GreaterOrEqualf)@this).New(a, b), isExact); } partial record Lessf @@ -356,31 +166,17 @@ partial record Lessf // For non-real complex numbers, they evaluate to NaN. private protected override Entity IntrinsicCondition => Left.In(MathS.Sets.R) & Right.In(MathS.Sets.R); - + /// - protected override Entity InnerEval() - => ExpandOnTwoArguments(Left.Evaled, Right.Evaled, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Left, Right, (a, b) => (a, b) switch { - (Real nan, _) when nan == MathS.NaN => MathS.NaN, - (_, Real nan) when nan == MathS.NaN => MathS.NaN, (Real reLeft, Real reRight) => reLeft < reRight, (Number numLeft, Number numRight) => MathS.NaN, _ => null }, - (@this, a, b) => ((Lessf)@this).New(a, b) - ); - - /// - protected override Entity InnerSimplify() - => Evaled is Boolean b ? b : - ExpandOnTwoArguments(Left.InnerSimplified, Right.InnerSimplified, - (a, b) => (a, b) switch - { - _ => null - }, - (@this, a, b) => ((Lessf)@this).New(a, b) - ); + (@this, a, b) => ((Lessf)@this).New(a, b), isExact); } partial record LessOrEqualf @@ -389,31 +185,16 @@ partial record LessOrEqualf // For non-real complex numbers, they evaluate to NaN. private protected override Entity IntrinsicCondition => Left.In(MathS.Sets.R) & Right.In(MathS.Sets.R); - /// - protected override Entity InnerEval() - => ExpandOnTwoArguments(Left.Evaled, Right.Evaled, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Left, Right, (a, b) => (a, b) switch { - (Real nan, _) when nan == MathS.NaN => MathS.NaN, - (_, Real nan) when nan == MathS.NaN => MathS.NaN, (Real reLeft, Real reRight) => reLeft <= reRight, (Number numLeft, Number numRight) => MathS.NaN, _ => null }, - (@this, a, b) => ((LessOrEqualf)@this).New(a, b) - ); - - /// - protected override Entity InnerSimplify() - => Evaled is Boolean b ? b : - ExpandOnTwoArguments(Left.InnerSimplified, Right.InnerSimplified, - (a, b) => (a, b) switch - { - _ => null - }, - (@this, a, b) => ((LessOrEqualf)@this).New(a, b) - ); + (@this, a, b) => ((LessOrEqualf)@this).New(a, b), isExact); } partial record Set @@ -422,28 +203,15 @@ partial record Inf { // Set membership is always defined for any element and set private protected override Entity IntrinsicCondition => True; - /// - protected override Entity InnerEval() - => ExpandOnTwoArguments(Element.Evaled, SupSet.Evaled, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Element, SupSet, (a, b) => (a, b) switch { (var el, Set set) when set.TryContains(el, out var contains) => contains, _ => null }, - (@this, a, b) => ((Inf)@this).New(a, b) - ); - - /// - protected override Entity InnerSimplify() - => ExpandOnTwoArguments(Element.InnerSimplified, SupSet.InnerSimplified, - (a, b) => (a, b) switch - { - (var el, Set set) when set.TryContains(el, out var contains) => contains, - _ => null - }, - (@this, a, b) => ((Inf)@this).New(a, b) - ); + (@this, a, b) => ((Inf)@this).New(a, b), isExact, propagateSet: false); } } @@ -452,29 +220,18 @@ partial record Phif // Euler's totient function is defined for all integers in this library. // For positive integers, it returns the standard φ(n) value. // For non-positive integers, this implementation extends the definition by returning 0. - private protected override Entity IntrinsicCondition => True; - - /// - protected override Entity InnerEval() - => ExpandOnOneArgument(Argument.Evaled, - a => a switch - { - Integer integer => integer.Phi(), - _ => null - }, - (@this, a) => ((Phif)@this).New(a) - ); + private protected override Entity IntrinsicCondition => Argument.In(MathS.Sets.Z); /// - protected override Entity InnerSimplify() - => ExpandOnOneArgument(Argument.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnOneArgument(Argument, a => a switch { Integer integer => integer.Phi(), + Number n => MathS.NaN, _ => null }, - (@this, a) => ((Phif)@this).New(a) - ); + (@this, a) => ((Phif)@this).New(a), isExact); } } } 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 3d22dbaa6..3dd1a52bd 100644 --- a/Sources/AngouriMath/Functions/Evaluation/Evaluation.Omni/Evaluation.Omni.Classes.cs +++ b/Sources/AngouriMath/Functions/Evaluation/Evaluation.Omni/Evaluation.Omni.Classes.cs @@ -7,6 +7,7 @@ using AngouriMath.Core.Sets; using System; +using static AngouriMath.Entity.Set; namespace AngouriMath { @@ -19,65 +20,40 @@ partial record FiniteSet { private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() - => Apply(el => el.Evaled); - - /// - protected override Entity InnerSimplify() - => Apply(el => el.InnerSimplified); + protected override Entity InnerSimplify(bool isExact) + => Apply(el => el.InnerSimplified(isExact)); } partial record Interval { private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() - => ExpandOnTwoAndTArguments(Left.Evaled, Right.Evaled, (l: LeftClosed, r: RightClosed), + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoAndTArguments(Left, Right, (l: LeftClosed, r: RightClosed), (a, b, lr) => (a, b, lr) switch { - (var left, var right, _) when left == right => lr.l && lr.r ? - new FiniteSet(Simplificator.PickSimplest(a, b)) : - Empty, + (var left, var right, _) when (isExact ? left.Evaled == right.Evaled : left == right) => + lr.l && lr.r + ? new FiniteSet(Simplificator.PickSimplest(left, right)) + : Empty, _ => null }, - (@this, a, b, lr) => ((Interval)@this).New(a, lr.l, b, lr.r), - false); - - /// - protected override Entity InnerSimplify() - => ExpandOnTwoAndTArguments(Left.InnerSimplified, Right.InnerSimplified, (l: LeftClosed, r: RightClosed), - (a, b, lr) => (a, b, lr) switch - { - (var left, var right, _) when left.Evaled == right.Evaled => lr.l && lr.r ? - new FiniteSet(Simplificator.PickSimplest(left, right)) : - Empty, - _ => null - }, - (@this, a, b, lr) => ((Interval)@this).New(a, lr.l, b, lr.r), - true); + (@this, a, b, lr) => ((Interval)@this).New(a, lr.l, b, lr.r), isExact); // NOTE: Intervals propagate set unlike other set operations } partial record ConditionalSet { private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() - => ExpandOnTwoAndTArguments(Var, Predicate.Evaled, this, - (@var, pred, @this) => - Simplificator.PickSimplest(@this.New(@var, pred), @this), - (@this, @var, pred, _) => ((ConditionalSet)@this).New(@var, pred) - ); - - /// - protected override Entity InnerSimplify() - => ExpandOnTwoAndTArguments(Var, Predicate.InnerSimplified, Codomain, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoAndTArguments(Var, Predicate, Codomain, (a, b, cod) => (a, b, cod) switch { - (var v, var pred, var codom) when pred.EvaluableBoolean && (bool)pred.EvalBoolean() => codom, - (var v, var pred, var codom) when pred.EvaluableBoolean && !(bool)pred.EvalBoolean() => Empty, + (_, { Evaled: Boolean(true) }, var codom) => codom, + (_, { Evaled: Boolean(false) }, var codom) => Empty, _ => null }, - (@this, @var, pred, _) => ((ConditionalSet)@this).New(@var, pred) + (@this, @var, pred, _) => ((ConditionalSet)@this).New(@var, pred), isExact, propagateSet: false ); } @@ -85,11 +61,7 @@ partial record SpecialSet { private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() - => this; - - /// - protected override Entity InnerSimplify() + protected override Entity InnerSimplify(bool isExact) => this; } @@ -97,12 +69,8 @@ partial record Unionf { private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() - => InnerSimplified; - - /// - protected override Entity InnerSimplify() - => ExpandOnTwoArguments(Left.InnerSimplified, Right.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Left, Right, (a, b) => (a, b) switch { (FiniteSet setLeft, Set setRight) => SetOperators.UniteFiniteSetAndSet(setLeft, setRight), @@ -111,20 +79,15 @@ protected override Entity InnerSimplify() (ConditionalSet csetLeft, ConditionalSet csetRight) => SetOperators.UniteCSetAndCSet(csetLeft, csetRight), _ => null }, - (@this, a, b) => ((Unionf)@this).New(a, b) - ); + (@this, a, b) => ((Unionf)@this).New(a, b), isExact, propagateSet: false); } partial record Intersectionf { private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() - => InnerSimplified; - - /// - protected override Entity InnerSimplify() - => ExpandOnTwoArguments(Left.InnerSimplified, Right.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Left, Right, (a, b) => (a, b) switch { (FiniteSet setLeft, Set setRight) => SetOperators.IntersectFiniteSetAndSet(setLeft, setRight), @@ -133,20 +96,15 @@ protected override Entity InnerSimplify() (ConditionalSet csetLeft, ConditionalSet csetRight) => SetOperators.IntersectCSetAndCSet(csetLeft, csetRight), _ => null }, - (@this, a, b) => ((Intersectionf)@this).New(a, b) - ); + (@this, a, b) => ((Intersectionf)@this).New(a, b), isExact, propagateSet: false); } partial record SetMinusf { private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() - => InnerSimplified; - - /// - protected override Entity InnerSimplify() - => ExpandOnTwoArguments(Left.InnerSimplified, Right.InnerSimplified, + protected override Entity InnerSimplify(bool isExact) + => ExpandOnTwoArguments(Left, Right, (a, b) => (a, b) switch { (Set setLeft, FiniteSet setRight) => SetOperators.SetSubtractSetAndFiniteSet(setLeft, setRight), @@ -154,96 +112,57 @@ protected override Entity InnerSimplify() (ConditionalSet csetLeft, ConditionalSet csetRight) => SetOperators.SetSubtractCSetAndCSet(csetLeft, csetRight), _ => null }, - (@this, a, b) => ((SetMinusf)@this).New(a, b) - ); + (@this, a, b) => ((SetMinusf)@this).New(a, b), isExact, propagateSet: false); } } partial record Providedf { private protected override Entity IntrinsicCondition => Predicate; - private Providedf SwitchOverChildren(Entity expression, Entity predicate) - => (expression, predicate) switch - { - (Providedf exprProvided, Providedf predProvided) => - New(exprProvided.Expression, exprProvided.Predicate & predProvided.Predicate & predProvided.Expression), - (var expr, Providedf predProvided) => - New(expr, predProvided.Predicate & predProvided.Expression), - (Providedf exprProvided, var pred) - => New(exprProvided.Expression, pred & exprProvided.Predicate), - (var expr, var pred) - => New(expr, pred) - }; - - private static Entity DecideWithPredicate(Providedf expr) + private Entity Decide(Entity expr, Entity predicate) { - if (expr.Predicate == Boolean.True) - return expr.Expression; - if (expr.Predicate == Boolean.False) + if (predicate.Evaled == Boolean.True) + return expr; + if (predicate.Evaled == Boolean.False || predicate.Evaled.IsNaN) return MathS.NaN; - return expr; - } - - /// - protected override Entity InnerEval() - { - var evaled = SwitchOverChildren(Expression.Evaled, Predicate.Evaled); - return DecideWithPredicate(evaled); + return New(expr, predicate); } - /// - protected override Entity InnerSimplify() - { - var simplified = SwitchOverChildren(Expression.InnerSimplified, Predicate.InnerSimplified); - return DecideWithPredicate(simplified); - } + protected override Entity InnerSimplify(bool isExact) => + ExpandOnTwoArguments(Expression, Predicate, + (a, b) => (a, b) switch + { + (Providedf exprProvided, Providedf predProvided) => + Decide(exprProvided.Expression, exprProvided.Predicate & predProvided.Predicate & predProvided.Expression), + (var expr, Providedf predProvided) => Decide(expr, predProvided.Predicate & predProvided.Expression), + (Providedf exprProvided, var pred) => Decide(exprProvided.Expression, pred & exprProvided.Predicate), + (var expr, var pred) => Decide(expr, pred), + }, + (@this, a, b) => ((Providedf)@this).New(a, b), isExact); } partial record Piecewise { private protected override Entity IntrinsicCondition => Cases.Aggregate((Entity?)null, (acc, curr) => acc is { } ? acc | curr.Predicate : curr.Predicate) ?? Boolean.False; - private Entity? ComputePiecewiseResultIfPossible() + /// + protected override Entity InnerSimplify(bool isExact) { foreach (var oneCase in Cases) { - if (oneCase.Predicate.Evaled is not Boolean) - return null; - if (oneCase.Predicate.Evaled == Boolean.True) - return oneCase.Expression; + if (oneCase.Predicate.Evaled is not Boolean) goto notYetDecidable; + if (oneCase.Predicate.Evaled == Boolean.True) return oneCase.Expression.InnerSimplified(isExact); } return MathS.NaN; - } - - private IEnumerable ProcessPiecewise(IEnumerable source) - { + notYetDecidable: var res = new List(); - foreach (var (@case, srcCase) in (Cases, source).Zip()) - if (@case.Predicate.Evaled == Boolean.True) - { - res.Add(srcCase); - break; - } - else if (@case.Predicate.Evaled != Boolean.False) - res.Add(srcCase); - return res; + 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; + res.Add(srcCase.Expression is Providedf(var inner, var pred) ? new Providedf(inner, (srcCase.Predicate & pred).InnerSimplified(isExact)) : srcCase); + if (@case.Predicate.Evaled == Boolean.True) break; + } + return New(res); } - - /// - protected override Entity InnerEval() - => ComputePiecewiseResultIfPossible() is { } expr - ? - expr.Evaled - : - New(ProcessPiecewise(Cases.Select(c => c.New(c.Expression.Evaled, c.Predicate.Evaled)))); - - /// - protected override Entity InnerSimplify() - => ComputePiecewiseResultIfPossible() is { } expr - ? - expr.InnerSimplified - : - New(ProcessPiecewise(Cases.Select(c => c.New(c.Expression.InnerSimplified, c.Predicate.InnerSimplified)))); } @@ -251,14 +170,9 @@ partial record Matrix { private protected override Entity IntrinsicCondition => Boolean.True; /// - protected override Entity InnerEval() - => IsScalar ? AsScalar().Evaled : - Elementwise(e => e.Evaled); - - /// - protected override Entity InnerSimplify() - => IsScalar ? AsScalar().InnerSimplified : - Elementwise(e => e.InnerSimplified); + protected override Entity InnerSimplify(bool isExact) + => IsScalar ? AsScalar().InnerSimplified(isExact) : + Elementwise(e => e.InnerSimplified(isExact)); } partial record Application @@ -272,8 +186,8 @@ private static Entity ApplyOthersIfNeeded(Entity outer, LList arguments) }; /// - protected override Entity InnerSimplify() - => ((Expression.InnerSimplified, Arguments.Map(arg => arg.InnerSimplified)) switch + protected override Entity InnerSimplify(bool isExact) + => ((Expression.InnerSimplified(isExact), Arguments.Map(arg => arg.InnerSimplified(isExact))) switch { (var identifier, LEmpty) => identifier, (Application(var any, var argsInner), var argsOuter) => any.Apply(argsInner.Concat(argsOuter).ToLList()), @@ -337,12 +251,8 @@ protected override Entity InnerSimplify() }) switch { var thisAgain when ReferenceEquals(thisAgain, this) => this, - var newOne => newOne.InnerSimplified + var newOne => newOne.InnerSimplified(isExact) }; - - /// - protected override Entity InnerEval() - => InnerSimplify(); } partial record Lambda @@ -362,8 +272,8 @@ partial record Lambda }; /// - protected override Entity InnerSimplify() - => (Parameter, Body.InnerSimplified) switch + protected override Entity InnerSimplify(bool isExact) + => (Parameter, Body.InnerSimplified(isExact)) switch { (var x1, Application(var expr, var args)) when !expr.FreeVariables.Contains(x1) @@ -373,12 +283,9 @@ protected override Entity InnerSimplify() LEmpty => expr, var rest => new Application(expr, rest) }, - (var x, var body) when body != Body => new Lambda(x, body).InnerSimplified, + (var x, var body) when body != Body => new Lambda(x, body).InnerSimplified(isExact), _ => this }; - /// - protected override Entity InnerEval() - => New(Parameter, Body.Evaled); } } } diff --git a/Sources/AngouriMath/Functions/Simplification/LiftProvided.cs b/Sources/AngouriMath/Functions/Simplification/LiftProvided.cs deleted file mode 100644 index 965b362d5..000000000 --- a/Sources/AngouriMath/Functions/Simplification/LiftProvided.cs +++ /dev/null @@ -1,175 +0,0 @@ -// -// Copyright (c) 2019-2022 Angouri. -// AngouriMath is licensed under MIT. -// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. -// Website: https://am.angouri.org. -// - -using AngouriMath.Core.Exceptions; -using System; -using System.Xml.Linq; -using static AngouriMath.Entity; -using static AngouriMath.Entity.Set; - -namespace AngouriMath.Functions -{ - internal static class ProvidedLifter - { - internal static Set MergePredicateIntoSolveResult(Set solveResult, Variable x, Entity predicate) => - solveResult switch - { - var s when predicate == Entity.Boolean.True => s, - FiniteSet f => f.Apply(e => e.Provided(predicate.Substitute(x, e))), - ConditionalSet c => new ConditionalSet(c.Var, c.Predicate & predicate.Substitute(x, c.Var)), - var s => new ConditionalSet(x, new Inf(x, s) & predicate) - }; - /// - /// Extracts all predicates to the top level. - /// - internal static bool ExtractProvidedPredicates(ref Entity expression, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out Entity? providedPredicate) - { - HashSet predicates = []; - expression = expression.LiftProvided(predicates); - if (predicates.Count == 0) - { - providedPredicate = null; - return false; - } - else - { - providedPredicate = predicates.Aggregate((acc, curr) => acc & curr); - return true; - } - } - } -} -namespace AngouriMath -{ - partial record Entity - { - - /// - /// Recursively lifts all nodes in the expression tree, - /// collecting their predicates into the provided set. This is the internal - /// implementation for . - /// - /// - /// A set that accumulates all provided predicates encountered during traversal. - /// - /// - /// The expression with all nodes removed. - /// - internal abstract Entity LiftProvided(HashSet predicates); - - public partial record Number { internal override Entity LiftProvided(HashSet predicates) => this; } - public partial record Variable { internal override Entity LiftProvided(HashSet predicates) => this; } - partial record Matrix { internal override Entity LiftProvided(HashSet predicates) => Elementwise(element => element.LiftProvided(predicates)); } - public partial record Sumf { internal override Entity LiftProvided(HashSet predicates) => New(Augend.LiftProvided(predicates), Addend.LiftProvided(predicates)); } - public partial record Minusf { internal override Entity LiftProvided(HashSet predicates) => New(Subtrahend.LiftProvided(predicates), Minuend.LiftProvided(predicates)); } - public partial record Mulf { internal override Entity LiftProvided(HashSet predicates) => New(Multiplier.LiftProvided(predicates), Multiplicand.LiftProvided(predicates)); } - public partial record Divf { internal override Entity LiftProvided(HashSet predicates) => New(Dividend.LiftProvided(predicates), Divisor.LiftProvided(predicates)); } - public partial record Powf { internal override Entity LiftProvided(HashSet predicates) => New(Base.LiftProvided(predicates), Exponent.LiftProvided(predicates)); } - public partial record Logf { internal override Entity LiftProvided(HashSet predicates) => New(Base.LiftProvided(predicates), Antilogarithm.LiftProvided(predicates)); } - public partial record Sinf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Cosf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Secantf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Cosecantf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Tanf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Cotanf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Arcsecantf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Arccosecantf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Arcsinf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Arccosf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Arctanf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Arccotanf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Factorialf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Derivativef { internal override Entity LiftProvided(HashSet predicates) => New(Expression.LiftProvided(predicates), Var.LiftProvided(predicates)); } - public partial record Integralf { internal override Entity LiftProvided(HashSet predicates) => New(Expression.LiftProvided(predicates), Var.LiftProvided(predicates)); } - public partial record Limitf { internal override Entity LiftProvided(HashSet predicates) => New(Expression.LiftProvided(predicates), Var.LiftProvided(predicates), Destination.LiftProvided(predicates), ApproachFrom); } - public partial record Signumf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Absf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Boolean { internal override Entity LiftProvided(HashSet predicates) => this; } - public partial record Notf { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - public partial record Andf { internal override Entity LiftProvided(HashSet predicates) => New(Left.LiftProvided(predicates), Right.LiftProvided(predicates)); } - public partial record Orf { internal override Entity LiftProvided(HashSet predicates) => New(Left.LiftProvided(predicates), Right.LiftProvided(predicates)); } - public partial record Xorf { internal override Entity LiftProvided(HashSet predicates) => New(Left.LiftProvided(predicates), Right.LiftProvided(predicates)); } - public partial record Impliesf { internal override Entity LiftProvided(HashSet predicates) => New(Assumption.LiftProvided(predicates), Conclusion.LiftProvided(predicates)); } - public partial record Equalsf { internal override Entity LiftProvided(HashSet predicates) => New(Left.LiftProvided(predicates), Right.LiftProvided(predicates)); } - public partial record Greaterf { internal override Entity LiftProvided(HashSet predicates) => New(Left.LiftProvided(predicates), Right.LiftProvided(predicates)); } - public partial record GreaterOrEqualf { internal override Entity LiftProvided(HashSet predicates) => New(Left.LiftProvided(predicates), Right.LiftProvided(predicates)); } - public partial record Lessf { internal override Entity LiftProvided(HashSet predicates) => New(Left.LiftProvided(predicates), Right.LiftProvided(predicates)); } - public partial record LessOrEqualf { internal override Entity LiftProvided(HashSet predicates) => New(Left.LiftProvided(predicates), Right.LiftProvided(predicates)); } - partial record Set - { - partial record FiniteSet - { - /// - /// For finite sets, lift provided predicates to the set boundary level. - /// Each element's conditions are kept with that element as a Providedf wrapper, - /// allowing Set.InnerSimplify to handle per-element conditions appropriately. - /// - internal override Entity LiftProvided(HashSet predicates) => - Apply(e => ProvidedLifter.ExtractProvidedPredicates(ref e, out var predicate) ? new Providedf(e, predicate) : e); - } - partial record Interval { internal override Entity LiftProvided(HashSet predicates) => New(Left.LiftProvided(predicates), LeftClosed, Right.LiftProvided(predicates), RightClosed); } - partial record ConditionalSet - { - /// - /// For conditional sets { x : P(x) }, merge any provided predicates from P(x) - /// into the predicate itself, since the predicate defines the set membership condition. - /// - internal override Entity LiftProvided(HashSet predicates) { - HashSet innerPredicates = []; - var newVar = Var.LiftProvided(innerPredicates); - var newPredicate = Predicate.LiftProvided(innerPredicates); - return New(newVar, innerPredicates.Aggregate(newPredicate, (acc, curr) => acc & curr)); - } - } - partial record SpecialSet { internal override Entity LiftProvided(HashSet predicates) => this; } - partial record Unionf { internal override Entity LiftProvided(HashSet predicates) => New(Left.LiftProvided(predicates), Right.LiftProvided(predicates)); } - partial record Intersectionf { internal override Entity LiftProvided(HashSet predicates) => New(Left.LiftProvided(predicates), Right.LiftProvided(predicates)); } - partial record SetMinusf { internal override Entity LiftProvided(HashSet predicates) => New(Left.LiftProvided(predicates), Right.LiftProvided(predicates)); } - partial record Inf { internal override Entity LiftProvided(HashSet predicates) => New(Element.LiftProvided(predicates), SupSet.LiftProvided(predicates)); } - } - partial record Phif { internal override Entity LiftProvided(HashSet predicates) => New(Argument.LiftProvided(predicates)); } - partial record Providedf - { - /// - /// The Providedf node itself lifts its predicate into the - /// accumulator set and returns its inner expression for continued traversal. - /// This is the core mechanism that extracts "expr provided P" into (expr, P). - /// - internal override Entity LiftProvided(HashSet predicates) - { - foreach (var p in Andf.LinearChildren(Predicate.LiftProvided(predicates))) - predicates.Add(p); - return Expression.LiftProvided(predicates); - } - } - partial record Piecewise { - /// - /// For piecewise functions, lift provided predicates to the boundary of each case. - /// Each case maintains its own (expression, condition) pair as a Providedf, - /// allowing proper handling of per-branch validity conditions. - /// - internal override Entity LiftProvided(HashSet predicates) => - Apply(provided => - { - HashSet innerPredicates = []; - var newExpr = provided.Expression.LiftProvided(innerPredicates); - var newPred = provided.Predicate.LiftProvided(innerPredicates); - return new Providedf(newExpr, innerPredicates.Aggregate(newPred, (acc, curr) => acc & curr)); - }); - } - partial record Application { internal override Entity LiftProvided(HashSet predicates) => New(Expression.LiftProvided(predicates), Arguments.Select(e => e.LiftProvided(predicates)).ToLList()); } - partial record Lambda { - - /// For lambdas, lift provided predicates to the lambdas boundary. - internal override Entity LiftProvided(HashSet predicates) - { - var body = Body; - return New(Parameter, ProvidedLifter.ExtractProvidedPredicates(ref body, out var predicate) ? new Providedf(body, predicate) : body); - } - } - } -} diff --git a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.EqualityInequality.cs b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.EqualityInequality.cs index 12cb253de..fe336e5d6 100644 --- a/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.EqualityInequality.cs +++ b/Sources/AngouriMath/Functions/Simplification/Patterns/Patterns.EqualityInequality.cs @@ -127,7 +127,7 @@ private static bool OppositeSigns(ComparisonSign left, ComparisonSign right) LessOrEqualf (Divf(var any1, var rePo), var zeroEnt) when IsRealNegative(rePo) && IsZero(zeroEnt) => any1 >= Integer.Zero, // a! = 0 - Equalsf(Factorialf({ DomainCondition: var condition }), var zeroEnt) when IsZero(zeroEnt) => False.WithCondition(condition), + Equalsf(Factorialf({ DomainCondition: var condition }), var zeroEnt) when IsZero(zeroEnt) => False.Provided(condition), Greaterf(var any1, var any1a) when any1 == any1a => false, Lessf(var any1, var any1a) when any1 == any1a => false, diff --git a/Sources/AngouriMath/Functions/Simplification/Simplificator.cs b/Sources/AngouriMath/Functions/Simplification/Simplificator.cs index 3754be8f2..6ac1100cb 100644 --- a/Sources/AngouriMath/Functions/Simplification/Simplificator.cs +++ b/Sources/AngouriMath/Functions/Simplification/Simplificator.cs @@ -84,8 +84,6 @@ void __IterAddHistory(Entity expr) 2 => TreeAnalyzer.SortLevel.LOW_LEVEL, _ => TreeAnalyzer.SortLevel.HIGH_LEVEL }; - if (res.Nodes.Any(child => child is Providedf) && ProvidedLifter.ExtractProvidedPredicates(ref res, out var predicate)) - AddHistory(res = new Providedf(res, predicate)); res = res.Replace(Patterns.SortRules(sortLevel)).InnerSimplified; if (res.Nodes.Any(child => child is Powf)) AddHistory(res = res.Replace(Patterns.PowerRules).InnerSimplified); diff --git a/Sources/Tests/UnitTests/Algebra/SolveTest/SolveEquationWithPiecewise.cs b/Sources/Tests/UnitTests/Algebra/SolveTest/SolveEquationWithPiecewise.cs index 33531e843..ec0d99ab9 100644 --- a/Sources/Tests/UnitTests/Algebra/SolveTest/SolveEquationWithPiecewise.cs +++ b/Sources/Tests/UnitTests/Algebra/SolveTest/SolveEquationWithPiecewise.cs @@ -6,6 +6,7 @@ // using AngouriMath.Extensions; +using System.Linq; using Xunit; using static AngouriMath.Entity.Set; @@ -18,15 +19,37 @@ public sealed class SolveEquationWithPiecewise "1 provided y > 0", "-5 provided y > 0", "2 provided y = 0 and not y > 0", "-4 provided y = 0 and not y > 0", "3 provided y < 0 and (not y > 0 and not y = 0)", "-3 provided y < 0 and (not y > 0 and not y = 0)")] + [InlineData("9 = piecewise((x + 2)2 provided y > 0, (x + 1)2 provided y = 0, x2 provided y < 0)", + "1 provided y > 0", "-5 provided y > 0", + "2 provided y = 0 and not y > 0", "-4 provided y = 0 and not y > 0", + "3 provided y < 0 and (not y > 0 and not y = 0)", "-3 provided y < 0 and (not y > 0 and not y = 0)")] + [InlineData("((x + 2)2 provided y > 0) = 9 or ((x + 1)2 provided y = 0) = 9 or (x2 provided y < 0) = 9", + "1 provided y > 0", "-5 provided y > 0", + "2 provided y = 0", "-4 provided y = 0", + "3 provided y < 0", "-3 provided y < 0")] + [InlineData("(9 = (x + 2)2 provided y > 0) or (9 = (x + 1)2 provided y = 0) or (9 = x2 provided y < 0)", + "1 provided y > 0", "-5 provided y > 0", + "2 provided y = 0", "-4 provided y = 0", + "3 provided y < 0", "-3 provided y < 0")] public void CheckIfRootWasObtained(string eq, string? r1 = null, string? r2 = null, string? r3 = null, string? r4 = null, string? r5 = null, string? r6 = null) { - var sols = eq.Solve("x"); - foreach (var expectedRoot in new[] { r1, r2, r3, r4, r5, r6 }) + var actualSols = (FiniteSet)eq.Solve("x"); + var expected = + new[] { r1, r2, r3, r4, r5, r6 } + .Where(r => r is not null) + .Select(r => r!.ToEntity()); + var expectedSols = new FiniteSet(expected); + foreach (var expectedRoot in expected) if (expectedRoot is not null) - Assert.True(((FiniteSet)sols).Contains(expectedRoot), $"Root {expectedRoot} expected to be in {sols}"); + { + Assert.True(actualSols.TryContains(expectedRoot, out var contains) && contains, $"Root {expectedRoot} expected to be in the solutions"); + Assert.True(actualSols.Contains(expectedRoot), $"Root {expectedRoot} expected to be in the solutions"); + } + Assert.True(expectedSols.Equals(actualSols)); + Assert.Equal(expectedSols.ToHashSet(), actualSols.ToHashSet()); } [Theory] [InlineData("((x + a) provided a > 0) = 5", "{ -(a + -5) provided a > 0 }")] // Linear with provided, should preserve condition diff --git a/Sources/Tests/UnitTests/Common/InnerSimplifyTest.cs b/Sources/Tests/UnitTests/Common/InnerSimplifyTest.cs index 062466fff..7289410f9 100644 --- a/Sources/Tests/UnitTests/Common/InnerSimplifyTest.cs +++ b/Sources/Tests/UnitTests/Common/InnerSimplifyTest.cs @@ -148,7 +148,7 @@ [Fact] public void IntegralToDerivativeSimplify2() => [Fact] public void IntegralToDerivativeSimplifyMinValue() => $"integral(apply(f, x), x, {int.MinValue})".ToEntity().InnerSimplified.ShouldBe($"integral(apply(f, x), x, {int.MinValue})"); [Fact] public void IntegralToDerivativeEval() => - "integral(apply(f, x) + (sin(3)^2 + cos(3)^2), x, -1)".ToEntity().Evaled.ShouldBe("derivative(apply(f, x), x) + 0"); + "integral(apply(f, x) + (sin(3)^2 + cos(3)^2), x, -1)".ToEntity().Evaled.ShouldBe("derivative(apply(f, x), x)"); [Fact] public void IntegralToDerivativeEval2() => "integral(apply(f, x), x, -2)".ToEntity().Evaled.ShouldBe("derivative(apply(f, x), x, 2)"); [Fact] public void IntegralToDerivativeEvalMinValue() => @@ -326,8 +326,11 @@ public void TestRepresentRational(string ratRaw, int countOfForms) [InlineData("9pi / 2")] [InlineData("5pi / 2")] public void TanShouldBeNaN(string angle) - => MathS.Tan(angle).Evaled.ShouldBe(MathS.NaN); - + { + MathS.Tan(angle).Evaled.ShouldBe(MathS.NaN); + MathS.Tan(angle).InnerSimplified.ShouldBe(MathS.NaN); + } + [Theory] [InlineData("pi / 2")] [InlineData("-pi / 2")] @@ -336,8 +339,11 @@ public void TanShouldBeNaN(string angle) [InlineData("9pi / 2")] [InlineData("5pi / 2")] public void SecShouldBeNaN(string angle) - => MathS.Sec(angle).Evaled.ShouldBe(MathS.NaN); - + { + MathS.Sec(angle).Evaled.ShouldBe(MathS.NaN); + MathS.Sec(angle).InnerSimplified.ShouldBe(MathS.NaN); + } + [Theory] [InlineData("pi")] [InlineData("-pi")] @@ -346,8 +352,11 @@ public void SecShouldBeNaN(string angle) [InlineData("9pi")] [InlineData("5pi")] public void CotanShouldBeNaN(string angle) - => MathS.Cotan(angle).Evaled.ShouldBe(MathS.NaN); - + { + MathS.Cotan(angle).Evaled.ShouldBe(MathS.NaN); + MathS.Cotan(angle).InnerSimplified.ShouldBe(MathS.NaN); + } + [Theory] [InlineData("pi")] [InlineData("-pi")] @@ -356,7 +365,10 @@ public void CotanShouldBeNaN(string angle) [InlineData("9pi")] [InlineData("5pi")] public void CosecShouldBeNaN(string angle) - => MathS.Cosec(angle).Evaled.ShouldBe(MathS.NaN); + { + MathS.Cosec(angle).Evaled.ShouldBe(MathS.NaN); + MathS.Cosec(angle).InnerSimplified.ShouldBe(MathS.NaN); + } [Theory] [InlineData("1 / 0 > 0")] @@ -371,24 +383,12 @@ public void CosecShouldBeNaN(string angle) [InlineData("i < i")] [InlineData("i >= (i + 1)")] [InlineData("i <= (i + 1)")] - public void InequalityShouldBeNaN(string expr) - => expr.ToEntity().Evaled.ShouldBe(MathS.NaN); - - [Theory] - [InlineData("1 / 0 > 0")] - [InlineData("1 / 0 < 0")] - [InlineData("1 / 0 >= 0")] - [InlineData("1 / 0 <= 0")] - [InlineData("0 < 1 / 0")] - [InlineData("0 > 1 / 0")] - [InlineData("0 <= 1 / 0")] - [InlineData("0 >= 1 / 0")] - [InlineData("i > i")] - [InlineData("i < i")] [InlineData("i >= -i")] [InlineData("i <= -i")] - public void InequalityShouldBeKeptInnerSimplify(string expr) - => expr.ToEntity().InnerSimplified.ShouldBe(expr); + public void InequalityShouldBeNaN(string expr) { + expr.ToEntity().Evaled.ShouldBe(MathS.NaN); + expr.ToEntity().InnerSimplified.ShouldBe(MathS.NaN); + } } } diff --git a/Sources/Tests/UnitTests/Core/Sets/Contains.cs b/Sources/Tests/UnitTests/Core/Sets/Contains.cs index 0089a775f..9dd8a754f 100644 --- a/Sources/Tests/UnitTests/Core/Sets/Contains.cs +++ b/Sources/Tests/UnitTests/Core/Sets/Contains.cs @@ -14,14 +14,27 @@ namespace AngouriMath.Tests.Core.Sets public sealed class Contains { [Theory] + [InlineData("{}", "2", false)] [InlineData("{}", "a", false)] + [InlineData("{}", "{}", false)] [InlineData("{ a }", "a", true)] + [InlineData("{ a }", "{}", false, false)] [InlineData("{ 1, 2, 3 }", "1", true)] [InlineData("{ 1, 2, 3 }", "{ 1 }", false)] [InlineData("{ 1, 2, 3, 4 }", "3.5", false)] + [InlineData("{ 1, 2, 3, x^3 }", "x^3", true)] + [InlineData("{ 1, 2, 3, { x^3 } }", "3", true)] + [InlineData("{ 1, 2, x, { x^3 } }", "x", true)] + [InlineData("{ 1, 2, 3, { x^3 } }", "{ x^3 }", true)] [InlineData("{ a, b, 1 }", "a", true)] [InlineData("{ a, b, { 1 } }", "{ 1 }", true)] [InlineData("{ a, b, { 1, b } }", "{ 1, b }", true)] + [InlineData("{ a, b, { 1, b } }", "2", false, false)] + [InlineData("{ a, b, { 1, b } }", "x", false, false)] + [InlineData("{ a, b, { 1, b } }", "x^3", false, false)] + [InlineData("{ a, b, { 1, b } }", "2b", false, false)] + [InlineData("{ 1, 2 }", "x^3", false, false)] + [InlineData("{ 1, 2, { x } }", "x^3", false, false)] [InlineData("[0; 3)", "2", true)] [InlineData("[3; -3]", "0", false)] [InlineData("[2; 2]", "2", true)] @@ -36,13 +49,17 @@ public sealed class Contains [InlineData("[2; a]", "a", true)] [InlineData("[b; a)", "b", true)] [InlineData("(b; a)", "b", false)] + [InlineData("(b; a)", "x", false, false)] + [InlineData("({1,2}; {3,4})", "x", false, false)] [InlineData("{ x : x > 0 }", "3", true)] [InlineData("{ x : x < 0 }", "3", false)] [InlineData("{ x : x = a }", "a", true)] + [InlineData("{ x : x = a }", "b", false, false)] [InlineData("RR", "3", true)] [InlineData("RR", "-3", true)] [InlineData("RR", "-0.3243", true)] [InlineData("RR", "3 + i", false)] + [InlineData("RR", "x", false, false)] [InlineData("CC", "3 + i", true)] [InlineData("BB", "true", true)] [InlineData("BB", "false", true)] @@ -53,12 +70,29 @@ public sealed class Contains [InlineData("ZZ", "3 / 4", false)] [InlineData("ZZ", "8", true)] [InlineData("ZZ", "-8", true)] - public void Test(string given, string expected, bool containsExpected) + [InlineData("ZZ \\ {-8}", "-8", false)] + [InlineData("{x} /\\ {y}", "1", false, false)] + [InlineData("{x} /\\ {y}", "z", false, false)] + [InlineData("{x} /\\ {1}", "1", false, false)] + [InlineData("{x} /\\ {1}", "x", false, false)] + [InlineData("{1} /\\ {1}", "1", true)] + [InlineData("{x} /\\ {x}", "x", true)] + [InlineData("{1} \\ {y}", "1", false, false)] + [InlineData("{1} \\ {y}", "z", false, false)] + [InlineData("{1} \\ {1}", "1", false)] + [InlineData("{1} \\ {1}", "x", false, false)] // can't determine x in {1} + [InlineData("{x} \\ {x}", "1", false, false)] // can't determine 1 in {x} + [InlineData("{x} \\ {x}", "x", false)] + [InlineData("{x} \\ {y}", "1", false, false)] + [InlineData("{x} \\ {y}", "z", false, false)] + [InlineData("{x} \\ {1}", "1", false, false)] + [InlineData("{x} \\ {1}", "x", false, false)] + public void Test(string given, string expected, bool containsExpected, bool tryContainsExpected = true) { var actualRaw = given.ToEntity(); var actual = Assert.IsAssignableFrom(actualRaw); var simplified = expected.ToEntity().InnerSimplified; - Assert.True(actual.TryContains(simplified, out var containsActual)); + Assert.Equal(tryContainsExpected, actual.TryContains(simplified, out var containsActual)); Assert.Equal(containsExpected, containsActual); } diff --git a/Sources/Tests/UnitTests/Discrete/EqualityInequalityEval.cs b/Sources/Tests/UnitTests/Discrete/EqualityInequalityEval.cs index 66c3d839a..139dbbdd8 100644 --- a/Sources/Tests/UnitTests/Discrete/EqualityInequalityEval.cs +++ b/Sources/Tests/UnitTests/Discrete/EqualityInequalityEval.cs @@ -48,6 +48,7 @@ public void IsFalse(string expr) [Theory] [InlineData("3 + i > 3")] + [InlineData("3 + sqrt(-1) > 3")] [InlineData("sqrt(-1) < 3")] [InlineData("i < 3")] [InlineData("i < i")] @@ -58,10 +59,40 @@ public void IsFalse(string expr) [InlineData("1 > i")] [InlineData("1 <= i")] [InlineData("1 >= i")] + [InlineData("0 / 0 = 0")] + [InlineData("0 = 0 / 0")] + [InlineData("0 / 0 = 0 / 0")] + [InlineData("0 / 0 <= 0")] + [InlineData("0 <= 0 / 0")] + [InlineData("0 / 0 <= 0 / 0")] + [InlineData("0 / 0 > 0")] + [InlineData("0 > 0 / 0")] + [InlineData("0 / 0 > 0 / 0")] + [InlineData("0 / 0 >= 0")] + [InlineData("0 >= 0 / 0")] + [InlineData("0 / 0 >= 0 / 0")] + [InlineData("not 0 / 0 = 0 / 0")] + [InlineData("x = 0 and 0 / 0 = 0 / 0")] + [InlineData("0 / 0 = 0 / 0 and x = 0")] + [InlineData("0 / 0 = 0 / 0 and 0 / 0 = 0 / 0")] + [InlineData("0 / 0 = 0 / 0 or x = 0")] + [InlineData("x = 0 or 0 / 0 = 0 / 0")] + [InlineData("0 / 0 = 0 / 0 or 0 / 0 = 0 / 0")] + [InlineData("0 / 0 = 0 / 0 xor x = 0")] + [InlineData("x = 0 xor 0 / 0 = 0 / 0")] + [InlineData("0 / 0 = 0 / 0 xor 0 / 0 = 0 / 0")] + [InlineData("0 / 0 = 0 / 0 implies x = 0")] + [InlineData("x = 0 implies 0 / 0 = 0 / 0")] + [InlineData("0 / 0 = 0 / 0 implies 0 / 0 = 0 / 0")] + [InlineData("0 / 0 in { }")] + [InlineData("0 = 0 ^ (-2 + i)")] + [InlineData("0 ^ 0 = 0")] + [InlineData("0 ^ 0 = 0 ^ (-2 + i)")] public void IsNaN(string expr) { var ent = expr.ToEntity(); Assert.Equal(MathS.NaN, ent.Evaled); + Assert.Equal(MathS.NaN, ent.InnerSimplified); } } } diff --git a/Sources/Tests/UnitTests/Discrete/InSet.cs b/Sources/Tests/UnitTests/Discrete/InSet.cs index 379fb6faf..8a742b9ea 100644 --- a/Sources/Tests/UnitTests/Discrete/InSet.cs +++ b/Sources/Tests/UnitTests/Discrete/InSet.cs @@ -16,19 +16,29 @@ public sealed class InSet [Theory] [InlineData(@"x in {}", false)] [InlineData(@"x in { x }", true)] + [InlineData(@"2 in { x }", null)] [InlineData(@"1 in { 2, 3 }", false)] [InlineData(@"1 in { 2, 3 } \/ { 3, 5 }", false)] [InlineData(@"5 in { 2, 3 } \/ { 3, 5 }", true)] [InlineData(@"a in { 2, 3 } \/ { 3, 5 } \/ [1; a]", true)] + [InlineData(@"a in [1; a] \/ { 2, 3 } \/ { 3, 5 } ", true)] + [InlineData(@"a in [0; 1] \/ [a; b] \/ [2; 3] ", true)] + [InlineData(@"b in [0; 1] \/ [a; b] \/ [2; 3] ", true)] + [InlineData(@"1 in [0; 1] \/ [a; b] \/ [2; 3] ", true)] + [InlineData(@"1.5 in [0; 1] \/ [a; b] \/ [2; 3] ", null)] + [InlineData(@"3 in [0; 1] \/ [a; b] \/ [2; 3] ", true)] + [InlineData(@"c in [0; 1] \/ [a; b] \/ [2; 3] ", null)] + [InlineData(@"a in { 2, 3, 5 } \/ [1; x]", null)] + [InlineData(@"x in [2; 3]", null)] [InlineData(@"3 in [2; 3]", true)] [InlineData(@"2 in [2; 3]", true)] [InlineData(@"2.5 in [2; 3]", true)] [InlineData(@"1.9 in [2; 3]", false)] [InlineData(@"3.1 in [2; 3]", false)] - public void TestInnerSimplify(string input, bool expected) + public void TestInnerSimplify(string input, bool? expected) { var ent = input.ToEntity().InnerSimplified; - Assert.Equal((Entity)expected, ent); + Assert.Equal((Entity)(expected ?? ent), ent); } } } diff --git a/Sources/Tests/UnitTests/PatternsTest/SetSimplify.cs b/Sources/Tests/UnitTests/PatternsTest/SetSimplify.cs index 1ff4217cd..fe2f9e802 100644 --- a/Sources/Tests/UnitTests/PatternsTest/SetSimplify.cs +++ b/Sources/Tests/UnitTests/PatternsTest/SetSimplify.cs @@ -34,7 +34,15 @@ public sealed class SetSimplify [InlineData("x2 in (x2 + 1/2; x2 + 1]", "false")] [InlineData("x2 in (x2 - 1/2; x2 + 1]", "true")] [InlineData("domain((-oo; +oo), RR)", "RR")] - [InlineData("x in {a}", "false")] + [InlineData("1 in {2}", "false")] + [InlineData("1 in {1, 2, a}", "true")] + [InlineData("3 in {1, 2, a}", "3 in {1, 2, a}")] + [InlineData("a in {1, 2, a}", "true")] + [InlineData("x in {a}", "x = a")] + [InlineData("4 in {1, 2, 3}", "false")] + [InlineData("{3} in {1, 2, 3}", "false")] + [InlineData("{3} in {1, 2, a}", "{3} in {1, 2, a}")] + [InlineData("{3} in {1, 2, {3}}", "true")] [InlineData("{ x : x in RR } in (-oo; +oo)", "false")] public void TestSimplify(string unsimplified, string simplified) { @@ -45,6 +53,7 @@ public void TestSimplify(string unsimplified, string simplified) } [Theory] [InlineData("{ 0/0 }", "{}")] + [InlineData("{ 1, -1/0, 0/0, 1/0, 2 }", "{ 1, 2 }")] [InlineData("{ 1 provided false }", "{}")] [InlineData("{ 1 provided true, 2 provided false, 3 provided true }", "{ 1, 3 }")] public void TestNaN(string unsimplified, string simplified) From 89a73080ab7e97fc89708846543d6e032c62a82a Mon Sep 17 00:00:00 2001 From: Hadrian Tang Date: Tue, 13 Jan 2026 06:22:15 +0800 Subject: [PATCH 2/2] Fix test --- .../Solvers/EquationSolver/AnalyticalEquationSolver.cs | 8 +------- .../UnitTests/Algebra/SolveTest/SolveOneEquation.cs | 10 +++++----- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs index 35fe9f2fb..7f23da6ce 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs @@ -88,7 +88,7 @@ internal static class AnalyticalEquationSolver /// Variable to solve over internal static Set Solve(Entity expr, Variable x, bool compensateSolving = false) { - expr = expr.InnerSimplified; + if (!compensateSolving) expr = expr.InnerSimplified; // don't simplify away the 0 on the right hand side of the subtraction if (expr == x) return new Entity[] { 0 }.ToSet(); @@ -110,12 +110,6 @@ static Entity TryDowncast(Entity equation, Variable x, Entity root) switch (expr) { - case Mulf(var multiplier, var multiplicand): - return MathS.Union(Solve(multiplier, x), Solve(multiplicand, x)); - case Divf(var dividend, var divisor): - return MathS.SetSubtraction(Solve(dividend, x), Solve(divisor, x)); - case Powf(var @base, _): - return Solve(@base, x); case Minusf(var subtrahend, var minuend) when !minuend.ContainsNode(x) && compensateSolving: if (subtrahend == x) return new[] { minuend }.ToSet(); diff --git a/Sources/Tests/UnitTests/Algebra/SolveTest/SolveOneEquation.cs b/Sources/Tests/UnitTests/Algebra/SolveTest/SolveOneEquation.cs index b03e61751..0b191516c 100644 --- a/Sources/Tests/UnitTests/Algebra/SolveTest/SolveOneEquation.cs +++ b/Sources/Tests/UnitTests/Algebra/SolveTest/SolveOneEquation.cs @@ -178,11 +178,11 @@ public void InvertedFunctions(string func, int rootAmount) public void LinearTrigRoots(string expr, int rootCount) => TestSolver(expr, rootCount); [Theory] - [InlineData("(x - b) / (x + a) + c", 1)] - [InlineData("(x - b) / (x + a) + c / (x + a)", 1)] - [InlineData("(x - b) / (x + a) + c / (x + a)2", 2)] - [InlineData("(x - b) / (x + a) + c + (x - c) / (x + d)", 2, 11)] - public void CDSolver(string expr, int rootCount, int? toSub = null) => TestSolver(expr, rootCount, toSub); + [InlineData("(x - b) / (x + a) + c", 1, "{ -(a * c + -b) / (1 + c) provided not -(a * c + -b) / (1 + c) + a = 0 }")] + [InlineData("(x - b) / (x + a) + c / (x + a)", 1, "{ -(c + -b) provided not a + -(c + -b) = 0 }")] + [InlineData("(x - b) / (x + a) + c / (x + a)2", 2, "{ (-(-b + a) - sqrt((-b + a) ^ 2 - 4 * (a * -b + c))) / 2 provided not (-(-b + a) - sqrt((-b + a) ^ 2 - 4 * (a * -b + c))) / 2 + a = 0, (-(-b + a) + sqrt((-b + a) ^ 2 - 4 * (a * -b + c))) / 2 provided not (-(-b + a) + sqrt((-b + a) ^ 2 - 4 * (a * -b + c))) / 2 + a = 0 }")] + [InlineData("(x - b) / (x + a) + c + (x - c) / (x + d)", 2, null)] + public void CDSolver(string expr, int rootCount, string? verifyRoots) => TestSolver(expr, rootCount, verifyRoots: verifyRoots); [Theory] [InlineData("x + sqrt(x^0.1 + a) + c", 0)]