From 2ff471383242e1a8bcb050aaf486ded6e3349028 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 26 Oct 2026 14:02:17 +0000 Subject: [PATCH] A system with more or fewer equations than unknowns is solved or declined EquationSystem.Solve threw WrongNumberOfArgumentsException for a non-square system unless the triangularisation took it (polynomial over Q, rational solutions) or it was short and linear with rational coefficients. The exception says the call was wrong, and it was not: x^2 + y^2 = 1 for x and y has a family of solutions, and sin(x) = 0 with x = 0 has one. Happypig375 agreed on #212 that accepting new forms and not throwing is fine. Such a system now goes to NonSquareSystemSolver. It eliminates the last unknown from the equation with the fewest occurrences of it, recurses on each root, and makes an unknown that nothing is left to constrain a parameter, t_1, as the linear path does. With no unknown left, an equation that is a number away from zero drops its branch; one that cannot be decided, like (a + b)/2 - c, makes that equation's roots no answer, and the next equation is tried. Every family is substituted back into every equation and kept only where each one vanishes, and a system with one that does not is declined as null, which Solve documents as no solution found. Two cases need that check: - sqrt(y) = x is solved for y as x^2, which is no solution for a negative x; sqrt(t^2) - t does not simplify to 0, so the system is declined rather than answered (t, t^2). - A coefficient that is zero without being written so: t^2 - c (t^2/c) simplifies to 0 provided not c = 0 for c = (a + 1)^2 - a^2 - 2a - 1. A provided condition counts only where it holds with the symbols pinned, so that family is declined too. A square system does not reach this, so no square answer changes. Measured, each non-square shape probed on 2.6.0 and on this branch: x^2 + y^2 - 1 in x, y exception -> [[t_1, sqrt(-(t_1 ^ 2 + -1))], [t_1, -sqrt(-(t_1 ^ 2 + -1))]] a*x + y - 1 in x, y exception -> [[t_1, -(a * t_1 + -1)]] sin(x); x in x exception -> [0] sin(x) - y; cos(x) - y; x - pi/4 exception -> [[pi / 4, 1/2 * sqrt(2)]] sqrt(y) - x in x, y exception -> null x + y - a; x - y - b; x - c in x, y exception -> null Part of https://github.com/asc-community/AngouriMath/issues/212 Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_012sonx8iAspMiwRwokT1Ura --- BREAKING-CHANGES.md | 20 ++ Sources/.editorconfig | 6 + Sources/AngouriMath/Core/EquationSystem.cs | 5 +- Sources/AngouriMath/Docs/Usage/Exceptions.md | 6 +- .../Continuous/Solvers/EquationSolver.cs | 12 +- .../EquationSolver/NonSquareSystemSolver.cs | 231 ++++++++++++++++++ .../Algebra/SolveTest/NonSquareSystemTest.cs | 109 +++++++++ .../Algebra/SolveTest/ParametricSystemTest.cs | 17 -- 8 files changed, 382 insertions(+), 24 deletions(-) create mode 100644 Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/NonSquareSystemSolver.cs create mode 100644 Sources/Tests/UnitTests/Algebra/SolveTest/NonSquareSystemTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 83a62c6da..aad6eccdf 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -42,6 +42,26 @@ A not-equal written `<>` is read as `not (a = b)`, and printed that way. ## Unreleased — since 2.6.0 +### A system with more or fewer equations than unknowns is solved or declined + +**Not silent: what was an exception is an answer or `null`.** `EquationSystem.Solve` threw +`WrongNumberOfArgumentsException` for a system with more or fewer equations than unknowns, unless it was polynomial +over the rationals with rational solutions or had fewer equations and was linear with rational coefficients. Such a +system is now eliminated one unknown at a time, and an unknown that nothing is left to constrain becomes a parameter, +`t_1`, as in the linear case. Every row is substituted back into every equation and kept only where each simplifies to +zero. Where one does not, or where a branch cannot be decided, the system is declined as `null`, which `Solve` +documents as no solution found ([#212](https://github.com/asc-community/AngouriMath/issues/212)). Code that caught the +exception around `Solve` to detect a non-square system no longer sees it. + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `MathS.Equations("x^2 + y^2 - 1").Solve("x", "y")` | `WrongNumberOfArgumentsException` | `[[t_1, sqrt(-(t_1 ^ 2 + -1))], [t_1, -sqrt(-(t_1 ^ 2 + -1))]]` | +| `MathS.Equations("a*x + y - 1").Solve("x", "y")` | `WrongNumberOfArgumentsException` | `[[t_1, -(a * t_1 + -1)]]` | +| `MathS.Equations("sin(x)", "x").Solve("x")` | `WrongNumberOfArgumentsException` | `[0]` | +| `MathS.Equations("sin(x) - y", "cos(x) - y", "x - pi/4").Solve("x", "y")` | `WrongNumberOfArgumentsException` | `[[pi / 4, 1/2 * sqrt(2)]]` | +| `MathS.Equations("sqrt(y) - x").Solve("x", "y")` | `WrongNumberOfArgumentsException` | `null`: `y = x^2` is no solution where `x` is negative | +| `MathS.Equations("x + y - a", "x - y - b", "x - c").Solve("x", "y")` | `WrongNumberOfArgumentsException` | `null`: there are solutions only where `a + b = 2c` | + ### `am_classify` is a preliminary tool, served by `amcli mcp --preliminary` **Not silent: what answered is a failure naming the flag.** `am_classify` guesses which field a formula comes from by diff --git a/Sources/.editorconfig b/Sources/.editorconfig index 45b05543c..b1324ec83 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -252,9 +252,15 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [AngouriMath/Functions/Continuous/Solvers/EquationSolver/LinearSystemSolver.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[AngouriMath/Functions/Continuous/Solvers/EquationSolver/NonSquareSystemSolver.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [AngouriMath/Functions/Continuous/Solvers/EquationSolver/ModulusSolver.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[Tests/UnitTests/Algebra/SolveTest/NonSquareSystemTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [Tests/UnitTests/Algebra/SolveTest/ParametricSystemTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Core/EquationSystem.cs b/Sources/AngouriMath/Core/EquationSystem.cs index 65175390e..1dfe06237 100644 --- a/Sources/AngouriMath/Core/EquationSystem.cs +++ b/Sources/AngouriMath/Core/EquationSystem.cs @@ -67,7 +67,10 @@ private static Entity AsAnExpressionEqualToZero(Entity equation) /// Solves a system of equations, if possible. /// /// - /// The umber of variables must match number of equations + /// The unknowns, as many as there are equations or not. Where there are more, or where a + /// system leaves one free, it becomes a parameter in the answer: x^2 + y^2 = 1 for + /// x and y is two rows, x = t_1 with y the positive and the + /// negative square root of 1 - t_1^2. /// /// /// If there exists at least one solution, returns a solution matrix diff --git a/Sources/AngouriMath/Docs/Usage/Exceptions.md b/Sources/AngouriMath/Docs/Usage/Exceptions.md index fb1045c7b..d493560d4 100644 --- a/Sources/AngouriMath/Docs/Usage/Exceptions.md +++ b/Sources/AngouriMath/Docs/Usage/Exceptions.md @@ -216,9 +216,9 @@ largest base there are digits for, and the base that was asked for. ### `WrongNumberOfArgumentsException` An API call whose arity does not match: a compiled function called with the wrong number of values, -a truth table asked for over a different number of variables than the expression has, a system of -equations with more or fewer equations than unknowns. Where the expression is in scope the message -names it and its variables. +or a truth table asked for over a different number of variables than the expression has. Where the +expression is in scope the message names it and its variables. A system of equations with more or +fewer equations than unknowns is not one: it is solved, or declined as `null`. Compare `FunctionArgumentCountException`, which is the same complaint about a function call written inside a string. **Use `FunctionArgumentCountException` from the parser and this one from diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver.cs index de4091918..902ebc064 100644 --- a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver.cs +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver.cs @@ -127,8 +127,8 @@ internal static Set Solve(Entity equation, Variable x) if (Groebner.GroebnerSystemSolver.TrySolve(equations, variables, out var triangularised)) return triangularised; - // Fewer equations than unknowns is not a mistake by the caller, which is what - // the exception below says it is. A linear system of that shape has infinitely + // Fewer equations than unknowns is not a mistake by the caller, which is what an + // exception here used to say it was. A linear system of that shape has infinitely // many solutions and one family that describes them all, so it is answered as // that family -- the unknowns a row reduction leaves free become parameters. // Only the short count is taken here: a square system that is rank-deficient @@ -139,8 +139,14 @@ internal static Set Solve(Entity equation, Variable x) equations, variables, out var family)) return family; + // Nor is any other count: `x^2 + y^2 = 1` has a family of solutions in a + // parameter, and `sin(x) = 0` with `x = 0` has one solution. Those are eliminated, + // and a system whose answer cannot be shown to hold is declined, as `Solve` + // documents. https://github.com/asc-community/AngouriMath/issues/212 if (equations.Count != vars.Length) - throw new WrongNumberOfArgumentsException("Number of equations must be equal to that of vars"); + return NonSquareSystemSolver.Solve(equations, variables, ledger) is { } families + ? new MatrixBuilder(families, vars.Length).ToMatrix() + : null; int initVarCount = vars.Length; var res = InSolveSystem(equations, vars, Sumf.Sum(equations), ledger); diff --git a/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/NonSquareSystemSolver.cs b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/NonSquareSystemSolver.cs new file mode 100644 index 000000000..4f8b648e6 --- /dev/null +++ b/Sources/AngouriMath/Functions/Continuous/Solvers/EquationSolver/NonSquareSystemSolver.cs @@ -0,0 +1,231 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using AngouriMath.Core.Budgets; +using AngouriMath.Core.Exceptions; +using System; +using static AngouriMath.Entity; +using static AngouriMath.Entity.Number; +using static AngouriMath.Entity.Set; + +namespace AngouriMath.Functions.Algebra.AnalyticalSolving +{ + /// + /// A system with more or fewer equations than unknowns that neither the triangularisation nor + /// takes, such as x^2 + y^2 = 1 for x and + /// y, or sin(x) = 0 and x = 0 for x. Each unknown is eliminated in + /// turn, an unknown that nothing is left to constrain becomes a parameter, and every family + /// found is substituted back into every equation. + /// #212 + /// + /// + /// + /// An answer is given only where it is shown to hold. The elimination trusts each solve + /// it makes, and a solve can answer for a case its equation does not cover: sqrt(y) = x + /// is solved for y as x^2, which is no solution where x is negative. So + /// each family is kept only where every equation, with the family substituted, simplifies to + /// 0. sqrt(t^2) - t does not, and that system is declined. + /// + /// + /// A decline declines the whole system. An equation left with no unknown in it is zero, + /// a number away from zero, or neither as far as can be told, like (a + b)/2 - c. A + /// branch where one is away from zero has no solutions and is dropped. A branch where one + /// cannot be told gives no answer for the system, rather than leaving the other branches to + /// read as all of its solutions. + /// + /// + /// A square system does not come here. It is the eliminator's in + /// , whose answers this would change. + /// + /// + internal static class NonSquareSystemSolver + { + /// + /// The solutions of in , a list of + /// values in their order for each family, or where none were found. + /// + internal static List>? Solve(List equations, Variable[] variables, BudgetLedger ledger) + { + // One parameter per unknown that is left free, the same in every branch. Each is + // minted against the equations and the parameters already minted, since CreateUnique + // compares whole names in use and would otherwise hand back the same name twice. + var parameters = new Variable?[variables.Length]; + Entity nameSource = equations.Count > 0 ? Sumf.Sum(equations) : Integer.Zero; + Variable ParameterFor(int index) + { + if (parameters[index] is { } minted) + return minted; + var parameter = Variable.CreateUnique(nameSource, "t"); + nameSource += parameter; + return parameters[index] = parameter; + } + + if (Eliminate(equations, variables, variables.Length, ParameterFor, ledger) is not { Count: > 0 } families) + return null; + foreach (var family in families) + foreach (var equation in equations) + { + var substituted = equation; + for (var index = 0; index < variables.Length; index++) + substituted = substituted.Substitute(variables[index], family[index]); + if (!Vanishes(substituted)) + return null; + } + return families; + } + + /// + /// The solutions in the first of , each + /// a list of their values in order. Empty where there are none, and + /// where that could not be told. + /// + private static List>? Eliminate(List equations, Variable[] variables, int count, + Func parameterFor, BudgetLedger ledger) + { + // Charged as InSolveSystem charges it: one unit for each candidate explored. + if (!ledger.Spend()) + throw new NotSufficientlySupportedException( + "this system of equations is not solvable within the budget. Raise " + + "MathS.Settings.Budget to allow more, or pass a cancellation token with " + + "MathS.Multithreading.SetLocalCancellationToken"); + + // No unknown is left, so each equation holds as it stands or does not. + if (count == 0) + { + var told = true; + foreach (var equation in equations) + switch (Holds(equation)) + { + case false: + return new(); + case null: + told = false; + break; + } + return told ? new() { new() } : null; + } + + // No equation is left, so every unknown left is free. + if (equations.Count == 0) + { + var free = new List(); + for (var index = 0; index < count; index++) + free.Add(parameterFor(index)); + return new() { free }; + } + + var variable = variables[count - 1]; + // The equation with the fewest occurrences of the unknown first, which is solved by + // inverting it and has the plainest roots: of `cos(x) = sin(x)` and `x = pi/4`, the + // first is solved as ln(sqrt(i))/i and the second as pi/4. + var candidates = Enumerable.Range(0, equations.Count) + .Where(i => equations[i].ContainsNode(variable)) + .OrderBy(i => equations[i].Nodes.Count(node => node == variable)) + .ThenBy(i => equations[i].Complexity) + .ToList(); + foreach (var i in candidates) + { + // An empty set is passed over as InSolveSystem passes it over: substituting an + // earlier unknown can leave an occurrence that cancels, and then the solve has + // nothing to say about this one. + if (equations[i].SolveEquation(variable).InnerSimplified is not FiniteSet roots || roots.Count == 0) + continue; + var rest = new List(equations); + rest.RemoveAt(i); + if (EachRoot(roots, rest, variables, count, parameterFor, ledger) is { } solutions) + return solutions; + // This equation's roots left a question that could not be told. Another + // equation's may not: `sin(x) = 0` gives x = 2 pi n_1, where `x = 0` gives 0. + } + + if (candidates.Count > 0) + return null; + // Nothing constrains this unknown, so it is a parameter in every solution of the rest. + var others = Eliminate(equations, variables, count - 1, parameterFor, ledger); + others?.ForEach(family => family.Add(parameterFor(count - 1))); + return others; + } + + /// + /// The solutions where the last of the unknowns is each of in + /// turn, or where that could not be told for one of them. + /// + private static List>? EachRoot(FiniteSet roots, List rest, Variable[] variables, int count, + Func parameterFor, BudgetLedger ledger) + { + var variable = variables[count - 1]; + var solutions = new List>(); + foreach (var root in roots) + { + var substituted = rest.Select(equation => equation.Substitute(variable, root)).ToList(); + if (Eliminate(substituted, variables, count - 1, parameterFor, ledger) is not { } families) + return null; + foreach (var family in families) + { + var value = root; + for (var index = 0; index < count - 1; index++) + value = value.Substitute(variables[index], family[index]); + family.Add(value.InnerSimplified); + solutions.Add(family); + } + } + return solutions; + } + + /// + /// Whether , with no unknown left in it, holds: + /// where it simplifies to zero, where it is a + /// number away from zero, and where neither can be told. + /// + private static bool? Holds(Entity equation) + { + if (Vanishes(equation)) + return true; + if (equation.Vars.Any() || equation.Evaled is not Complex value || value.IsNaN) + return null; + // An exact number that is not zero is not zero. A decimal is taken as away from zero + // only well past the digits its evaluation may have lost. + if (value is Rational) + return false; + return ((Real)value.Abs()).EDecimal.ToDouble() > 1e-9 ? false : null; + } + + /// + /// Whether is zero for every value of its symbols: zero as it + /// is evaluated, or simplified to zero. A simplification to 0 provided C counts only + /// where C holds with the symbols pinned, since t - c (t/c) simplifies to + /// 0 provided not c = 0 for a c that is zero without being written + /// 0, such as (a + 1)^2 - a^2 - 2a - 1. + /// + private static bool Vanishes(Entity expression) + { + if (expression.InnerSimplified.Evaled is Complex { IsZero: true }) + return true; + // Away from zero at a point is not zero everywhere, which settles most without + // simplifying. + if (Pinned(expression).Evaled is Complex { IsNaN: false } atAPoint + && ((Real)atAPoint.Abs()).EDecimal.ToDouble() > 1e-9) + return false; + return expression.Simplify() switch + { + Providedf(var simplified, var condition) => + simplified.Evaled is Complex { IsZero: true } && Pinned(condition).Evaled is Entity.Boolean { Value: true }, + var simplified => simplified.Evaled is Complex { IsZero: true }, + }; + } + + /// with each of its symbols at a fixed value. + private static Entity Pinned(Entity expression) + { + var values = new[] { 1.37, 0.61, 2.23, 1.91, 0.83, 1.13 }; + var index = 0; + foreach (var symbol in expression.Vars.ToList()) + expression = expression.Substitute(symbol, values[index++ % values.Length]); + return expression; + } + } +} diff --git a/Sources/Tests/UnitTests/Algebra/SolveTest/NonSquareSystemTest.cs b/Sources/Tests/UnitTests/Algebra/SolveTest/NonSquareSystemTest.cs new file mode 100644 index 000000000..311a57b01 --- /dev/null +++ b/Sources/Tests/UnitTests/Algebra/SolveTest/NonSquareSystemTest.cs @@ -0,0 +1,109 @@ +// +// Copyright (c) 2019-2026 Angouri. +// AngouriMath is licensed under MIT. +// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md. +// Website: https://am.angouri.org. +// + +using AngouriMath; +using AngouriMath.Extensions; +using System.Linq; +using Xunit; + +namespace AngouriMath.Tests.Algebra +{ + /// + /// Systems with more or fewer equations than unknowns that are not linear over the + /// rationals, which answers. Each of these raised + /// WrongNumberOfArgumentsException, and is now answered or declined as + /// . + /// #212 + /// + [Trait("Area", "Algebra")] + public sealed class NonSquareSystemTest + { + private static Entity.Matrix? Solve(string[] equations, string[] variables) + => MathS.Equations(equations.Select(e => e.ToEntity()).ToArray()) + .Solve(variables.Select(v => (Entity.Variable)v).ToArray()); + + /// + /// Each row, substituted into each equation, is zero at several values of the parameters + /// and symbols left in it, negative ones among them. That is checked numerically rather + /// than by simplifying, which is how the solver checks it: sqrt(t^2) - t is zero at + /// every positive t and at no negative one. + /// + private static void HoldsAtSampledValues(string[] equations, string[] variables, Entity.Matrix solutions) + { + var samples = new[] { 1.37, -1.7, 2.3, -0.61 }; + for (var row = 0; row < solutions.RowCount; row++) + foreach (var text in equations) + { + Entity residual = text.ToEntity(); + for (var column = 0; column < variables.Length; column++) + residual = residual.Substitute(variables[column], solutions[row, column]); + var symbols = residual.Vars.ToList(); + for (var shift = 0; shift < samples.Length; shift++) + { + var pinned = residual; + for (var i = 0; i < symbols.Count; i++) + pinned = pinned.Substitute(symbols[i], samples[(shift + i) % samples.Length]); + var value = Assert.IsAssignableFrom(pinned.Evaled); + Assert.False(value.IsNaN, $"{residual} at {pinned}"); + Assert.True(((Entity.Number.Real)value.Abs()).EDecimal.ToDouble() < 1e-9, $"{residual} at {pinned} is {value}"); + } + } + } + + /// + /// Fewer equations than unknowns, not linear in them or with a symbol for a coefficient: + /// an unknown that is left free is a parameter, and a root with two values is two rows. + /// + [Theory] + [InlineData(new[] { "x^2 + y^2 - 1" }, new[] { "x", "y" }, 2)] + [InlineData(new[] { "x*y - 1" }, new[] { "x", "y" }, 1)] + [InlineData(new[] { "a*x + y - 1" }, new[] { "x", "y" }, 1)] + [InlineData(new[] { "sin(x) + y" }, new[] { "x", "y" }, 1)] + [InlineData(new[] { "e^x - y", "y - z" }, new[] { "x", "y", "z" }, 1)] + [InlineData(new[] { "x^2 + y - 1" }, new[] { "x", "y", "z" }, 1)] + public void AShortSystemIsAFamily(string[] equations, string[] variables, int rows) + { + var solutions = Solve(equations, variables); + Assert.NotNull(solutions); + Assert.Equal(rows, solutions!.RowCount); + Assert.Equal(variables.Length, solutions.ColumnCount); + HoldsAtSampledValues(equations, variables, solutions); + } + + /// + /// More equations than unknowns, which the triangularisation does not take because they + /// are not polynomial. The last drops x = -1, where sin(-pi/2) - 1 is -2. + /// + [Theory] + [InlineData(new[] { "sin(x)", "x" }, new[] { "x" }, new[] { "0" })] + [InlineData(new[] { "e^x - 1", "x + y", "y" }, new[] { "x", "y" }, new[] { "0", "0" })] + [InlineData(new[] { "sin(x) - y", "cos(x) - y", "x - pi/4" }, new[] { "x", "y" }, new[] { "pi/4", "sqrt(2)/2" })] + [InlineData(new[] { "x^2 - 1", "sin(pi*x/2) - 1" }, new[] { "x" }, new[] { "1" })] + public void AnOverdeterminedSystemIsSolved(string[] equations, string[] variables, string[] solution) + { + var solutions = Solve(equations, variables); + Assert.NotNull(solutions); + Assert.Equal(1, solutions!.RowCount); + for (var column = 0; column < variables.Length; column++) + Assert.Equal(Entity.Number.Integer.Create(0), (solutions[0, column] - solution[column]).Simplify()); + } + + /// + /// What the elimination finds and cannot show to hold. sqrt(y) = x is solved for + /// y as x^2, no solution where x is negative. The coefficient of + /// y in the second is zero without being written so, and dividing by it gives a + /// family that Simplify calls zero provided it is not zero. The third has solutions + /// only where a + b = 2c. + /// + [Theory] + [InlineData(new[] { "sqrt(y) - x" }, new[] { "x", "y" })] + [InlineData(new[] { "x^2 - ((a + 1)^2 - a^2 - 2*a - 1)*y" }, new[] { "x", "y" })] + [InlineData(new[] { "x + y - a", "x - y - b", "x - c" }, new[] { "x", "y" })] + public void WhatCannotBeShownToHoldIsDeclined(string[] equations, string[] variables) + => Assert.Null(Solve(equations, variables)); + } +} diff --git a/Sources/Tests/UnitTests/Algebra/SolveTest/ParametricSystemTest.cs b/Sources/Tests/UnitTests/Algebra/SolveTest/ParametricSystemTest.cs index 7c0281561..0de312f73 100644 --- a/Sources/Tests/UnitTests/Algebra/SolveTest/ParametricSystemTest.cs +++ b/Sources/Tests/UnitTests/Algebra/SolveTest/ParametricSystemTest.cs @@ -6,7 +6,6 @@ // using AngouriMath; -using AngouriMath.Core.Exceptions; using AngouriMath.Extensions; using System.Linq; using Xunit; @@ -124,22 +123,6 @@ public void AContradictoryShortSystemHasNoSolutions() => Assert.Null(MathS.Equations(new Entity[] { "x + y - 1", "2*x + 2*y - 3" }) .Solve("x", "y", "z")); - /// - /// What is not taken here, and still reaches the old refusal. Linearity is checked rather - /// than assumed, so a product or a power of the unknowns fails the check; and a - /// coefficient that is not rational is refused because a row reduction has to decide - /// whether a pivot is zero, which is not decidable for a symbol. - /// - [Theory] - [InlineData(new[] { "x^2 + y^2 - 1" }, new[] { "x", "y" })] - [InlineData(new[] { "x*y - 1" }, new[] { "x", "y" })] - [InlineData(new[] { "a*x + y - 1" }, new[] { "x", "y" })] - [InlineData(new[] { "sin(x) + y" }, new[] { "x", "y" })] - public void WhatIsNotLinearInTheUnknownsIsRefused(string[] equations, string[] variables) - => Assert.Throws( - () => MathS.Equations(equations.Select(e => e.ToEntity()).ToArray()) - .Solve(variables.Select(v => (Entity.Variable)v).ToArray())); - /// /// The neighbours must answer exactly as they did: a determined system, an /// overdetermined but consistent one, and a contradictory one. Only the short count