diff --git a/AngouriMath/Convenience/MathS.cs b/AngouriMath/Convenience/MathS.cs index 0bf0371a4..cf76bf829 100644 --- a/AngouriMath/Convenience/MathS.cs +++ b/AngouriMath/Convenience/MathS.cs @@ -18,6 +18,7 @@ using AngouriMath.Functions.Algebra.NumericalSolving; using AngouriMath.Functions.Boolean; using AngouriMath.Core.Exceptions; +using System.Diagnostics.CodeAnalysis; namespace AngouriMath.Core { @@ -862,6 +863,9 @@ public static class Boolean public static class Utils { + /// + /// Performs the expansion operation over the given variable + /// public static Entity SmartExpandOver(Entity expr, Variable x) { var linChildren = Sumf.LinearChildren(expr); @@ -878,6 +882,50 @@ public static Entity SmartExpandOver(Entity expr, Variable x) return TreeAnalyzer.MultiHangBinary(nodes, (a, b) => a + b); } + + /// + /// Extracts a polynomial with integer powers + /// + /// From which to extract the polynomial + /// Over which variable to extract the polynomial + /// + /// Where to put the dictionary, whose keys + /// are powers, and values - coefficients + /// + /// Whether the input expression is a valid polynomial + public static bool TryGetPolynomial(Entity expr, Variable variable, + [NotNullWhen(true)] out Dictionary? dst) + => TreeAnalyzer.TryGetPolynomial(expr, variable, out dst); + + /// + /// Extracts the linear coefficient and the bias over a variable + /// a x + b + /// + /// From which to extract the linear function + /// Over which to extract + /// The linear coefficient + /// The bias + /// Whether the extract was successful + public static bool TryGetPolyLinear(Entity expr, Variable variable, + [NotNullWhen(true)] out Entity? a, + [NotNullWhen(true)] out Entity? b) + => TreeAnalyzer.TryGetPolyLinear(expr, variable, out a, out b); + + /// + /// Extracts the quadratic coefficient, linear coefficient and the bias over a variable + /// a x ^ 2 + b x + c + /// + /// From which to extract the quadratic function + /// Over which to extract + /// The quadratic coefficient + /// The linear coefficient + /// The bias + /// Whether the extract was successful + public static bool TryGetPolyQuadratic(Entity expr, Variable variable, + [NotNullWhen(true)] out Entity? a, + [NotNullWhen(true)] out Entity? b, + [NotNullWhen(true)] out Entity? c) + => TreeAnalyzer.TryGetPolyQuadratic(expr, variable, out a, out b, out c); } } } diff --git a/AngouriMath/Functions/Continuous/Algebra/Integration.cs b/AngouriMath/Functions/Continuous/Algebra/Integration.cs index 187f94e75..426ce8815 100644 --- a/AngouriMath/Functions/Continuous/Algebra/Integration.cs +++ b/AngouriMath/Functions/Continuous/Algebra/Integration.cs @@ -47,7 +47,7 @@ public Complex DefiniteIntegral(Variable x, (EDecimal Re, EDecimal Im) from, (ED } namespace AngouriMath.Functions.Algebra { - internal static class Integration + public static partial class Integration { /// Numerical definite integration, see more in internal static Complex Integrate(Entity func, Entity.Variable x, (EDecimal Re, EDecimal Im) from, (EDecimal Re, EDecimal Im) to, int stepCount) diff --git a/AngouriMath/Functions/Continuous/Algebra/Integration/IndefiniteIntegralSolver.cs b/AngouriMath/Functions/Continuous/Algebra/Integration/IndefiniteIntegralSolver.cs new file mode 100644 index 000000000..e391e885a --- /dev/null +++ b/AngouriMath/Functions/Continuous/Algebra/Integration/IndefiniteIntegralSolver.cs @@ -0,0 +1,97 @@ +using System.Linq; + +namespace AngouriMath.Functions.Algebra +{ + static class IndefiniteIntegralSolver + { + internal static Entity? SolveBySplittingSum(Entity expr, Entity.Variable x) + { + var splitted = TreeAnalyzer.GatherLinearChildrenOverSumAndExpand(expr, e => e.ContainsNode(x)); + if (splitted is null || splitted.Count < 2) return null; // nothing to do, let other solvers do the work + splitted[0] = Integration.ComputeIndefiniteIntegral(splitted[0], x); // base case for aggregate + var result = splitted.Aggregate((e1, e2) => e1 + Integration.ComputeIndefiniteIntegral(e2, x)); + return result; + } + + internal static Entity? SolveAsPolynomialTerm(Entity expr, Entity.Variable x) => expr switch + { + Entity.Mulf(var m1, var m2) => + !m1.ContainsNode(x) ? + m1 * Integration.ComputeIndefiniteIntegral(m2, x) : + !m2.ContainsNode(x) ? + m2 * Integration.ComputeIndefiniteIntegral(m1, x) : + null, + + Entity.Divf(var div, var over) => + !div.ContainsNode(x) ? + over is Entity.Powf(var @base, var power) ? + div * Integration.ComputeIndefiniteIntegral(MathS.Pow(@base, -power), x) : + div * Integration.ComputeIndefiniteIntegral(MathS.Pow(over, -1), x) : + !over.ContainsNode(x) ? + Integration.ComputeIndefiniteIntegral(div, x) / over : + null, + + Entity.Powf(var @base, var power) => + !power.ContainsNode(x) && @base == x ? + power == -1 ? + MathS.Ln(@base) : // TODO: here should be ln(abs(x)) but for now I left it as is + MathS.Pow(x, power + 1) / (power + 1) : + null, + + Entity.Variable(var v) => + v == x ? MathS.Pow(x, 2) / 2 : v * x, + + _ => null + }; + + internal static Entity? SolveIntegratingByParts(Entity expr, Entity.Variable x) + { + static Entity? IntegrateByParts(Entity v, Entity u, Entity.Variable x, int currentRecursion = 0) + { + if (v == 0) return 0; + if (currentRecursion == MathS.Settings.MaxExpansionTermCount) return null; + + var integral = Integration.ComputeIndefiniteIntegral(u, x); + var differential = v.Derive(x).InnerSimplify(); + var result = IntegrateByParts(differential, integral, x, currentRecursion + 1); + return (result is null) ? null : v * integral - result; + } + + if (expr is Entity.Mulf(var f, var g)) + { + if (MathS.TryPolynomial(f, x, out var fPoly)) + { + return IntegrateByParts(fPoly, g, x); + } + if (MathS.TryPolynomial(g, x, out var gPoly)) + { + return IntegrateByParts(gPoly, f, x); + } + else return null; + } + else return null; + } + + internal static Entity? SolveLogarithmic(Entity expr, Entity.Variable x) => expr switch + { + Entity.Logf(var @base, var arg) => + @base.ContainsNode(x) ? + Integration.ComputeIndefiniteIntegral(MathS.Ln(arg) / MathS.Ln(@base), x) : + arg is Entity.Powf(var y, var pow) ? // log(b, y^p) = ln(y^p) / ln(b) = ln(p) / ln(b) * ln(y) + Integration.ComputeIndefiniteIntegral(pow / MathS.Ln(@base) * MathS.Ln(y), x) : + null, + + _ => null + }; + + internal static Entity? SolveExponential(Entity expr, Entity.Variable x) => expr switch + { + Entity.Powf(var @base, var pow) => + @base.ContainsNode(x) ? + Integration.ComputeIndefiniteIntegral(MathS.Pow(MathS.e, MathS.Ln(@base) * pow), x) : + null, + + _ => null + }; + } +} diff --git a/AngouriMath/Functions/Continuous/Algebra/Integration/IntegralPatterns.cs b/AngouriMath/Functions/Continuous/Algebra/Integration/IntegralPatterns.cs new file mode 100644 index 000000000..0be45e765 --- /dev/null +++ b/AngouriMath/Functions/Continuous/Algebra/Integration/IntegralPatterns.cs @@ -0,0 +1,40 @@ +namespace AngouriMath.Functions.Algebra +{ + internal static class IntegralPatterns + { + internal static Entity? TryStandardIntegrals(Entity expr, Entity.Variable x) => expr switch + { + Entity.Sinf(var arg) => + TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) ? + -MathS.Cos(arg) / a : + null, + + Entity.Cosf(var arg) => + TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) ? + MathS.Sin(arg) / a : + null, + + Entity.Tanf(var arg) => + TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) ? + -MathS.Ln(MathS.Cos(arg)) / a : + null, + + Entity.Cotanf(var arg) => + TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out _) ? + MathS.Ln(MathS.Sin(arg)) / a : + null, + + Entity.Logf(var @base, var arg) => + !@base.ContainsNode(x) && TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out var b) ? + ((b / a + x) * MathS.Ln(arg) - x) / MathS.Ln(@base) : + null, + + Entity.Powf(var @base, var power) => + !@base.ContainsNode(x) && TreeAnalyzer.TryGetPolyLinear(power, x, out var a, out _) ? + MathS.Pow(@base, power) / (a * MathS.Ln(@base)) : + null, + + _ => null + }; + } +} diff --git a/AngouriMath/Functions/Continuous/Algebra/Integration/Integration.cs b/AngouriMath/Functions/Continuous/Algebra/Integration/Integration.cs new file mode 100644 index 000000000..41d1d87f2 --- /dev/null +++ b/AngouriMath/Functions/Continuous/Algebra/Integration/Integration.cs @@ -0,0 +1,29 @@ +namespace AngouriMath.Functions.Algebra +{ + public static partial class Integration + { + public static Entity ComputeIndefiniteIntegral(Entity expr, Entity.Variable x) + { + if (!expr.ContainsNode(x)) return expr * x; // base case, handle here + + Entity? answer = null; + + answer = IntegralPatterns.TryStandardIntegrals(expr, x); + if (answer is { }) return answer; + + answer = IndefiniteIntegralSolver.SolveBySplittingSum(expr, x); + if (answer is { }) return answer; + + answer = IndefiniteIntegralSolver.SolveAsPolynomialTerm(expr, x); + if (answer is { }) return answer; + + answer = IndefiniteIntegralSolver.SolveIntegratingByParts(expr, x); + if (answer is { }) return answer; + + answer = IndefiniteIntegralSolver.SolveLogarithmic(expr, x); + if (answer is { }) return answer; + + return new Entity.Integralf(expr, x, 1); // return as integral if nothing can be done with expression + } + } +} diff --git a/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs b/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs index 3ab4d6d68..064a7497c 100644 --- a/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs +++ b/AngouriMath/Functions/Continuous/Solvers/EquationSolver/AnalyticalEquationSolver.cs @@ -147,12 +147,22 @@ static Entity TryDowncast(Entity equation, Variable x, Entity root) // // // } - // if no replacement worked, try trigonometry solver + // if no replacement worked, try trigonometric solver if (TrigonometricSolver.TrySolveLinear(expr, x, out var trig) && trig is FiniteSet elsTrig) return (Set)elsTrig.Select(ent => TryDowncast(expr, x, ent)).ToSet().InnerSimplified; // // // - // if no trigonometric rules helped, common denominator might help + // if no trigonometric rules helped, try exponential-multiplicative solver + if (ExponentialSolver.SolveMultiplicative(expr, x) is { } expMul && expMul is FiniteSet elsExpMul) + return (Set)elsExpMul.Select(ent => TryDowncast(expr, x, ent)).ToSet().InnerSimplified; + // // // + + // if no exponential-multiplicative rules helped, try exponential-linear solver + if (ExponentialSolver.SolveLinear(expr, x) is { } expLin && expLin is FiniteSet elsExpLin) + return (Set)elsExpLin.Select(ent => TryDowncast(expr, x, ent)).ToSet().InnerSimplified; + // // // + + // if no exponential-linear rules helped, common denominator might help if (CommonDenominatorSolver.TrySolveGCD(expr, x, out var commonDenom) && commonDenom is FiniteSet elsCd) return (Set)elsCd.Select(ent => TryDowncast(expr, x, ent)).ToSet().InnerSimplified; // // // diff --git a/AngouriMath/Functions/Continuous/Solvers/EquationSolver/ExponentialSolver.cs b/AngouriMath/Functions/Continuous/Solvers/EquationSolver/ExponentialSolver.cs new file mode 100644 index 000000000..0de23bb7a --- /dev/null +++ b/AngouriMath/Functions/Continuous/Solvers/EquationSolver/ExponentialSolver.cs @@ -0,0 +1,92 @@ +using AngouriMath.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml; +using static AngouriMath.Entity.Set; + +namespace AngouriMath.Functions.Algebra.AnalyticalSolving +{ + class ExponentialSolver + { + internal static Entity.Set? SolveLinear(Entity expr, Entity.Variable x) + { + var replacement = Entity.Variable.CreateTemp(expr.Vars); + + Func preparator = e => e switch + { + Entity.Powf(var @base, var arg) when + TreeAnalyzer.TryGetPolyLinear(arg, x, out var a, out var b) => + MathS.Pow(MathS.e, b) * MathS.Pow(MathS.Pow(MathS.e, x), MathS.Ln(@base) * a), + + _ => e, + }; + + Func replacer = e => e switch + { + Entity.Powf(var @base, var arg) + when @base == MathS.e && arg == x + => replacement, + + _ => e, + }; + + expr = expr.Replace(preparator); + expr = expr.Replace(replacer); + + if (expr.ContainsNode(x)) return null; // cannot be solved, not a pure exponential + + expr = expr.InnerSimplify(); + if (AnalyticalEquationSolver.Solve(expr, replacement) is FiniteSet els && els.Any()) + return (Entity.Set)els.Select(sol => MathS.Pow(MathS.e, x).Invert(sol, x).ToSet()).Unite().InnerSimplified; + else + return null; + } + + internal static Entity.Set? SolveMultiplicative(Entity expr, Entity.Variable x) + { + Entity? substitution = null; + Entity ApplyPowerTransform(Entity @base, Entity arg) + { + var mults = Entity.Mulf.LinearChildren(arg); + if (mults.Count() == 0) return MathS.Pow(@base, arg); + + Entity innerPower = 1; + Entity outerPower = 1; + foreach(var mult in mults) + { + if (mult.InnerSimplify() is Entity.Number) + outerPower = outerPower * mult; + else + innerPower = innerPower * mult; + } + substitution = MathS.Pow(@base, innerPower); + return MathS.Pow(substitution, outerPower.InnerSimplified); + } + + Func powerTransform = e => e switch + { + Entity.Powf(var @base, var arg) + when @base == x && !arg.ContainsNode(x) => + ApplyPowerTransform(@base, arg), + + _ => e, + }; + + expr = expr.Replace(powerTransform); + if (substitution is null) return null; + + var replacement = Entity.Variable.CreateTemp(expr.Vars); + expr = expr.Substitute(substitution, replacement); + + if (expr.ContainsNode(x)) return null; // cannot be solved, not a multiplicative exponenial equation + + expr = expr.InnerSimplify(); + if (AnalyticalEquationSolver.Solve(expr, replacement) is FiniteSet els && els.Any()) + return (Entity.Set)els.Select(sol => substitution.Invert(sol, x).ToSet()).Unite().InnerSimplified; + else + return null; + } + } +} diff --git a/AngouriMath/Functions/Continuous/Solvers/InequalitySolver/AnalyticalInequalitySolver.cs b/AngouriMath/Functions/Continuous/Solvers/InequalitySolver/AnalyticalInequalitySolver.cs index 601ef9d2c..f5aee2412 100644 --- a/AngouriMath/Functions/Continuous/Solvers/InequalitySolver/AnalyticalInequalitySolver.cs +++ b/AngouriMath/Functions/Continuous/Solvers/InequalitySolver/AnalyticalInequalitySolver.cs @@ -20,10 +20,7 @@ internal static class AnalyticalInequalitySolver /// internal static Set Solve(Entity expr, Variable x) { - if (expr is Minusf(var v, var c) && v == x && !c.ContainsNode(x)) - return new Interval(c, false, Number.Real.PositiveInfinity, false); - if (expr is Minusf(var c1, var v1) && v1 == x && !c1.ContainsNode(x)) - return new Interval(Number.Real.NegativeInfinity, false, c1, false); + // TODO: poly parser throw FutureReleaseException.Raised("Inequalities are not implemented yet", "1.2.1"); } } diff --git a/AngouriMath/Functions/Evaluation/Evaluation.Continuous.Classes.cs b/AngouriMath/Functions/Evaluation/Evaluation.Continuous.Classes.cs index 076571f62..629cb6823 100644 --- a/AngouriMath/Functions/Evaluation/Evaluation.Continuous.Classes.cs +++ b/AngouriMath/Functions/Evaluation/Evaluation.Continuous.Classes.cs @@ -13,6 +13,7 @@ using AngouriMath.Core; using AngouriMath.Core.Exceptions; using static AngouriMath.Entity.Set; +using AngouriMath.Functions.Algebra; namespace AngouriMath { @@ -373,21 +374,27 @@ Var.InnerSimplified is Variable var } public partial record Integralf { + private Entity SequentialIntegrating(Entity expr, Variable var, int iterations) + { + if (iterations < 0) + return this; + var changed = expr; + for (int i = 0; i < iterations; i++) + changed = Integration.ComputeIndefiniteIntegral(changed.InnerSimplified, var); + return changed; + } + protected override Entity InnerEval() => (Expression.Evaled, Var.Evaled, Iterations) switch { (Tensor expr, var var, var iters) => expr.Elementwise(n => new Integralf(n, var, iters).Evaled), (var expr, _, 0) => expr, // TODO: consider Derivative for negative cases - (var expr, Variable var, var asInt) => - throw FutureReleaseException.Raised("Integration is not implemented yet", "1.2.3"), + (var expr, Variable var, int asInt) => SequentialIntegrating(expr, var, asInt), + _ => this }; internal override Entity InnerSimplify() => - Var.InnerSimplified is Variable - ? Iterations == 0 - ? Expression.InnerSimplified - : throw FutureReleaseException.Raised("Integration is not implemented yet", "1.2.3") - : this; + Var.InnerSimplified is Variable var ? SequentialIntegrating(Expression.InnerSimplified, var, Iterations) : this; } public partial record Limitf { diff --git a/AngouriMath/Functions/Evaluation/Patterns.cs b/AngouriMath/Functions/Evaluation/Patterns.cs index 438b612e5..01cd1a02b 100644 --- a/AngouriMath/Functions/Evaluation/Patterns.cs +++ b/AngouriMath/Functions/Evaluation/Patterns.cs @@ -177,12 +177,12 @@ Entity FactorizeFactorialMultiplications(Entity x, Entity x2, Number factConst, internal static Func TrigonometricToExponentialRules(Variable from, Variable to) => tree => { // sin(ax + b) = (t^a * e^(i*b) - t^(-a) * e^(-i*b)) / (2i) - Entity SinResult(Variable x, Number a, Entity b) => + Entity SinResult(Variable x, Entity a, Entity b) => x == from ? MathS.Pow(to, a) * (MathS.Pow(MathS.e, b * MathS.i) / (2 * MathS.i)) - MathS.Pow(to, -a) * MathS.Pow(MathS.e, -b * MathS.i) / (2 * MathS.i) : tree; // cos(ax + b) = (t^a * e^(i*b) + t^(-a) * e^(-i*b)) / 2 - Entity CosResult(Variable x, Number a, Entity b) => + Entity CosResult(Variable x, Entity a, Entity b) => x == from ? MathS.Pow(to, a) * (MathS.Pow(MathS.e, b * MathS.i) / 2) + MathS.Pow(to, -a) * MathS.Pow(MathS.e, -b * MathS.i) / 2 : tree; @@ -191,36 +191,12 @@ Entity CosResult(Variable x, Number a, Entity b) => // e.g. tan(ax + b) = -i + (2i)/(1 + e^(2i*b) t^(2a)) return tree switch { - Sinf(Variable x) => SinResult(x, 1, 0), - Sinf(Mulf(Variable x, Number a)) => SinResult(x, a, 0), - Sinf(Mulf(Number a, Variable x)) => SinResult(x, a, 0), - Sinf(Sumf(Variable x, var b)) => SinResult(x, 1, b), - Sinf(Sumf(var b, Variable x)) => SinResult(x, 1, b), - Sinf(Sumf(Mulf(Variable x, Number a), var b)) => SinResult(x, a, b), - Sinf(Sumf(Mulf(Number a, Variable x), var b)) => SinResult(x, a, b), - Sinf(Sumf(var b, Mulf(Variable x, Number a))) => SinResult(x, a, b), - Sinf(Sumf(var b, Mulf(Number a, Variable x))) => SinResult(x, a, b), - Sinf(Minusf(Variable x, var b)) => SinResult(x, 1, -b), - Sinf(Minusf(var b, Variable x)) => SinResult(x, -1, b), - Sinf(Minusf(Mulf(Variable x, Number a), var b)) => SinResult(x, a, -b), - Sinf(Minusf(Mulf(Number a, Variable x), var b)) => SinResult(x, a, -b), - Sinf(Minusf(var b, Mulf(Variable x, Number a))) => SinResult(x, -a, b), - Sinf(Minusf(var b, Mulf(Number a, Variable x))) => SinResult(x, -a, b), - Cosf(Variable x) => CosResult(x, 1, 0), - Cosf(Mulf(Variable x, Number a)) => CosResult(x, a, 0), - Cosf(Mulf(Number a, Variable x)) => CosResult(x, a, 0), - Cosf(Sumf(Variable x, var b)) => CosResult(x, 1, b), - Cosf(Sumf(var b, Variable x)) => CosResult(x, 1, b), - Cosf(Sumf(Mulf(Variable x, Number a), var b)) => CosResult(x, a, b), - Cosf(Sumf(Mulf(Number a, Variable x), var b)) => CosResult(x, a, b), - Cosf(Sumf(var b, Mulf(Variable x, Number a))) => CosResult(x, a, b), - Cosf(Sumf(var b, Mulf(Number a, Variable x))) => CosResult(x, a, b), - Cosf(Minusf(Variable x, var b)) => CosResult(x, 1, -b), - Cosf(Minusf(var b, Variable x)) => CosResult(x, -1, b), - Cosf(Minusf(Mulf(Variable x, Number a), var b)) => CosResult(x, a, -b), - Cosf(Minusf(Mulf(Number a, Variable x), var b)) => CosResult(x, a, -b), - Cosf(Minusf(var b, Mulf(Variable x, Number a))) => CosResult(x, -a, b), - Cosf(Minusf(var b, Mulf(Number a, Variable x))) => CosResult(x, -a, b), + Sinf(var arg) => TreeAnalyzer.TryGetPolyLinear(arg, from, out var a, out var b) ? + SinResult(from, a.InnerSimplify(), b.InnerSimplify()) : tree, + + Cosf(var arg) => TreeAnalyzer.TryGetPolyLinear(arg, from, out var a, out var b) ? + CosResult(from, a.InnerSimplify(), b.InnerSimplify()) : tree, + _ => tree }; }; @@ -577,19 +553,15 @@ private static bool IsZero(Entity entity) // Suggestions to refactor this? private static bool OppositeSigns(ComparisonSign left, ComparisonSign right) - { - if (left is Lessf) - return right is Greaterf or GreaterOrEqualf or Equalsf; - if (left is LessOrEqualf) - return right is Greaterf; - if (left is Greaterf) - return right is Lessf or LessOrEqualf or Equalsf; - if (left is GreaterOrEqualf) - return right is Lessf; - if (left is Equalsf) - return right is Lessf or Greaterf; - return false; - } + => left switch + { + Lessf => right is Greaterf or GreaterOrEqualf or Equalsf, + LessOrEqualf => right is Greaterf, + Greaterf => right is Lessf or LessOrEqualf or Equalsf, + GreaterOrEqualf => right is Lessf, + Equalsf => right is Lessf or Greaterf, + _ => false + }; internal static Entity InequalityEqualityRules(Entity x) => x switch { diff --git a/AngouriMath/Functions/Evaluation/TreeAnalyzer/LongDivision.cs b/AngouriMath/Functions/Evaluation/TreeAnalyzer/LongDivision.cs index 068716321..9a173dbaf 100644 --- a/AngouriMath/Functions/Evaluation/TreeAnalyzer/LongDivision.cs +++ b/AngouriMath/Functions/Evaluation/TreeAnalyzer/LongDivision.cs @@ -17,11 +17,11 @@ namespace AngouriMath.Functions using static Entity.Number; internal static partial class TreeAnalyzer { - class PolynomialInformation + internal sealed class PolynomialInformation { - readonly Dictionary> monoInfo = new(); - readonly Dictionary replacements = new(); - readonly Dictionary revertReplacements = new(); + private readonly Dictionary> monoInfo = new(); + private readonly Dictionary replacements = new(); + private readonly Dictionary revertReplacements = new(); public IReadOnlyDictionary> MonoInfo => monoInfo; public IReadOnlyDictionary Replacements => replacements; public IReadOnlyDictionary RevertReplacements => revertReplacements; @@ -36,32 +36,34 @@ public void AddMonoInfo(Variable variable, Dictionary? powers) if (powers is { }) monoInfo.Add(variable, powers); } } + + internal static PolynomialInformation GatherAllPossiblePolynomials(Entity expr, bool replaceVars) + { + // Init + var res = new PolynomialInformation(); + + if (replaceVars) + { + // Replace all variables we can + foreach (var varMentioned in expr.Vars) + res.AddReplacement(expr.Vars, GetMinimumSubtree(expr, varMentioned)); + expr = expr.Substitute(res.Replacements); + } + + // Gather info about each var as if this var was the only argument of the polynomial P(x) + var children = Sumf.LinearChildren(expr); + foreach (var varMentioned in expr.Vars) + res.AddMonoInfo(varMentioned, Algebra.AnalyticalSolving.PolynomialSolver.GatherMonomialInformation + (children, varMentioned)); + return res; + } + /// /// Divides one polynomial over another one: /// /// internal static (Entity Divided, Entity Remainder)? PolynomialLongDivision(Entity p, Entity q) { - static PolynomialInformation GatherAllPossiblePolynomials(Entity expr, bool replaceVars) - { - // Init - var res = new PolynomialInformation(); - - if (replaceVars) - { - // Replace all variables we can - foreach (var varMentioned in expr.Vars) - res.AddReplacement(expr.Vars, GetMinimumSubtree(expr, varMentioned)); - expr = expr.Substitute(res.Replacements); - } - - // Gather info about each var as if this var was the only argument of the polynomial P(x) - var children = Sumf.LinearChildren(expr); - foreach (var varMentioned in expr.Vars) - res.AddMonoInfo(varMentioned, Algebra.AnalyticalSolving.PolynomialSolver.GatherMonomialInformation - (children, varMentioned)); - return res; - } if (!p.Vars.Any() || !q.Vars.Any()) return null; // There are no variables to find polynomial as diff --git a/AngouriMath/Functions/Evaluation/TreeAnalyzer/PolyParser.cs b/AngouriMath/Functions/Evaluation/TreeAnalyzer/PolyParser.cs new file mode 100644 index 000000000..367e41523 --- /dev/null +++ b/AngouriMath/Functions/Evaluation/TreeAnalyzer/PolyParser.cs @@ -0,0 +1,97 @@ + +/* Copyright (c) 2019-2020 Angourisoft + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software + * is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using AngouriMath.Core; +using AngouriMath.Core.Exceptions; +using PeterO.Numbers; + +namespace AngouriMath.Functions +{ + using static Entity; + using static Entity.Number; + internal static partial class TreeAnalyzer + { + /// + /// Finds all monomials with respect to the power of + /// + internal static bool TryGetPolynomial(Entity expr, Variable variable, + [NotNullWhen(true)] out Dictionary? dst) + { + var children = Sumf.LinearChildren(expr.Expand()); + dst = Algebra.AnalyticalSolving.PolynomialSolver.GatherMonomialInformation + (children, variable); + return dst is not null; + } + + /// a x + b + internal static bool TryGetPolyLinear(Entity expr, Variable variable, + [NotNullWhen(true)] out Entity? a, + [NotNullWhen(true)] out Entity? b) + { + a = b = null; + if (TryGetPolynomial(expr, variable, out var monoInfo) + && AssertIsValidNormalPoly(monoInfo, 1, out var res)) + { + a = res[1]; + b = res[0]; + return true; + } + return false; + } + + /// a x ^ 2 + b x + c + internal static bool TryGetPolyQuadratic(Entity expr, Variable variable, + [NotNullWhen(true)] out Entity? a, + [NotNullWhen(true)] out Entity? b, + [NotNullWhen(true)] out Entity? c) + { + a = b = c = null; + if (TryGetPolynomial(expr, variable, out var monoInfo) + && AssertIsValidNormalPoly(monoInfo, 2, out var res)) + { + a = res[2]; + b = res[1]; + c = res[0]; + return true; + } + return false; + } + + private static bool AssertIsValidNormalPoly(Dictionary polyInfo, + int maxPower, out Entity[] monoByPower) + { + monoByPower = new Entity[maxPower + 1]; + foreach (var pair in polyInfo) + { + if (!pair.Key.CanFitInInt32()) + return false; + var asInt = pair.Key.ToInt32Unchecked(); + if (asInt < 0) + return false; + if (asInt > maxPower) + return false; + monoByPower[asInt] = pair.Value; + } + for (int i = 0; i < monoByPower.Length; i++) + if (monoByPower[i] is null) + monoByPower[i] = 0; + return true; + } + } +} diff --git a/Samples/Samples/Program.cs b/Samples/Samples/Program.cs index bd7ba1c00..9663e489f 100644 --- a/Samples/Samples/Program.cs +++ b/Samples/Samples/Program.cs @@ -1,5 +1,11 @@ -using AngouriMath.Extensions; +using System; +using AngouriMath; +using static AngouriMath.Entity; +using static AngouriMath.Entity.Boolean; using static AngouriMath.MathS; +using AngouriMath.Extensions; using static System.Console; -WriteLine("a = b = c = d = e".ToEntity()); \ No newline at end of file +WriteLine("a = b = c = d = e".ToEntity()); +Console.WriteLine("4^x + 2^x - (2^x)^3 - a".SolveEquation("x")); + diff --git a/Tests/UnitTests/Core/PolyParser.cs b/Tests/UnitTests/Core/PolyParser.cs new file mode 100644 index 000000000..d57e052fd --- /dev/null +++ b/Tests/UnitTests/Core/PolyParser.cs @@ -0,0 +1,97 @@ +using AngouriMath; +using static AngouriMath.Entity.Number; +using Xunit; +using AngouriMath.Functions; + +namespace UnitTests.Core +{ + public class PolyParser + { + // Workaround to avoid ToString which results in SO in RC1 + private void AssertEntityEqual(Entity a, Entity b) + { + b = b.InnerSimplified; + if (a != b) + Assert.False(true, $"{a.Stringize()} is not {b.Stringize()}"); + } + + private void Fail() + { + Assert.True(false, "Not parsed for some reason"); + } + + [Fact] + public void TestLinear1() + { + if (MathS.Utils.TryGetPolyLinear("a x + b", "x", out var a, out var b)) + { + AssertEntityEqual("a", a); + AssertEntityEqual("b", b); + } + else + Fail(); + } + + [Fact] + public void TestLinear2() + { + if (MathS.Utils.TryGetPolyLinear("a x - b", "x", out var a, out var b)) + { + AssertEntityEqual("a", a); + AssertEntityEqual("-b", b); + } + else + Fail(); + } + + [Fact] + public void TestLinear3() + { + if (MathS.Utils.TryGetPolyLinear("a x - b + 3x + x", "x", out var a, out var b)) + { + AssertEntityEqual("a + 3 + 1", a); + AssertEntityEqual("-b", b); + } + else + Fail(); + } + + [Fact] + public void TestLinear4() + { + Assert.False(MathS.Utils.TryGetPolyLinear("a x - b + 3x + x2", "x", out _, out _)); + } + + [Fact] + public void TestLinear5() + { + Assert.False(MathS.Utils.TryGetPolyLinear("a x - b + 3x + x^(-999)", "x", out _, out _)); + } + + [Fact] + public void TestQuadratic1() + { + if (MathS.Utils.TryGetPolyQuadratic("a x2 + b x + c", "x", out var a, out var b, out var c)) + { + AssertEntityEqual("a", a); + AssertEntityEqual("b", b); + AssertEntityEqual("c", c); + } + else + Fail(); + } + + [Fact] + public void TestQuadratic2() + { + if (MathS.Utils.TryGetPolyQuadratic("a x2 + b x2 + c", "x", out var a, out var b, out var c)) + { + AssertEntityEqual("a + b", a); + AssertEntityEqual("0", b); + AssertEntityEqual("c", c); + } + else + Fail(); + } + } +}