diff --git a/Sources/Tests/FSharpWrapperUnitTests/FSharpWrapperUnitTests.fsproj b/Sources/Tests/FSharpWrapperUnitTests/FSharpWrapperUnitTests.fsproj index f6a42a3e7..f987569cf 100644 --- a/Sources/Tests/FSharpWrapperUnitTests/FSharpWrapperUnitTests.fsproj +++ b/Sources/Tests/FSharpWrapperUnitTests/FSharpWrapperUnitTests.fsproj @@ -17,6 +17,7 @@ + diff --git a/Sources/Tests/FSharpWrapperUnitTests/Functions.Systems.fs b/Sources/Tests/FSharpWrapperUnitTests/Functions.Systems.fs new file mode 100644 index 000000000..a6a7fd4f3 --- /dev/null +++ b/Sources/Tests/FSharpWrapperUnitTests/Functions.Systems.fs @@ -0,0 +1,38 @@ +module AngouriMath.FSharp.Tests.Systems + +open AngouriMath.FSharp.Core +open AngouriMath.FSharp.Functions +open Xunit + +// https://github.com/asc-community/AngouriMath/issues/562. Version 2 of the two shapes proposed +// there: the variables first and the equations second, which is how `solutions x expr` already +// reads, rather than a separate EquationSystem type the caller has to know about. `equationSystem` +// is still exposed for anyone who wants to build one and pass it around. + +[] +let ``A linear system is solved over its variables`` () = + match solveSystem ["x"; "y"] ["x + y - 3"; "x - y - 1"] with + | Some solutions -> + Assert.Equal(1, solutions.RowCount) + Assert.Equal(parsed "2", solutions.[0, 0]) // columns follow the order of vars + Assert.Equal(parsed "1", solutions.[0, 1]) + | None -> failwith "the system has a solution and should not have answered None" + +[] +let ``An equality may be written out rather than moved to one side`` () = + let asEquality = solveSystem ["x"; "y"] ["x + y = 3"; "x - y = 1"] + let asExpression = solveSystem ["x"; "y"] ["x + y - 3"; "x - y - 1"] + Assert.Equal(asExpression.IsSome, asEquality.IsSome) + Assert.Equal(asExpression.Value.[0, 0], asEquality.Value.[0, 0]) + Assert.Equal(asExpression.Value.[0, 1], asEquality.Value.[0, 1]) + +/// An inconsistent system has no solution, and None is that rather than an empty matrix. +[] +let ``An inconsistent system answers None`` () = + Assert.True((solveSystem ["x"; "y"] ["x + y - 1"; "x + y - 2"]).IsNone) + +[] +let ``A system can be built and passed around`` () = + let system = equationSystem ["x + y - 3"; "x - y - 1"] + Assert.NotNull(system) + Assert.True((system.Solve(symbol "x", symbol "y")) <> null) diff --git a/Sources/Wrappers/AngouriMath.FSharp/Functions.fs b/Sources/Wrappers/AngouriMath.FSharp/Functions.fs index b4ea3ab05..a8d294e00 100644 --- a/Sources/Wrappers/AngouriMath.FSharp/Functions.fs +++ b/Sources/Wrappers/AngouriMath.FSharp/Functions.fs @@ -250,3 +250,26 @@ let solutions x expr = match parsed expr with | :? Entity.Statement as statement -> statement.Solve(symbol x).InnerSimplified :?> Entity.Set | func -> (equality func 0).Solve(symbol x).InnerSimplified :?> Entity.Set + +/// Returns a system of equations, built from a list of equalities or expressions. +/// An expression that is not an equality is read as being equal to zero, exactly as +/// `solutions` reads a single one. +let equationSystem (equations : 'T list) = + MathS.Equations(equations |> List.map (fun eq -> + match parsed eq with + // EquationSystem reads each entry as being equal to zero, so an equality written out + // is moved to one side rather than handed over as a node -- passing the node makes the + // solver try to invert an equality, which it cannot do. + | :? Entity.Equalsf as equation -> equation.Left - equation.Right + | func -> func)) + +/// Solves a system of equations over the given variables. +/// +/// Each row of the resulting matrix is one solution and its columns follow the order of +/// `vars`, so `solveSystem ["x"; "y"] ["x + y - 3"; "x - y - 1"]` answers `[[2, 1]]`: +/// one solution, with x = 2 and y = 1. Returns None where none was found, rather than an +/// empty matrix, since "no solution" and "the solver gave up" are the same answer here and +/// neither is a matrix. +let solveSystem (vars : 'T list) (equations : 'U list) = + (equationSystem equations).Solve(vars |> List.map symbol |> Array.ofList) + |> Option.ofObj