Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<Compile Include="CompilationTest.fs" />
<Compile Include="Functions.Matrices.fs" />
<Compile Include="Functions.Order.fs" />
<Compile Include="Functions.Systems.fs" />
<Compile Include="Common.fs" />
<Compile Include="Functions.Discrete.fs" />
<Compile Include="Functions.Continuous.fs" />
Expand Down
38 changes: 38 additions & 0 deletions Sources/Tests/FSharpWrapperUnitTests/Functions.Systems.fs
Original file line number Diff line number Diff line change
@@ -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.

[<Fact>]
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"

[<Fact>]
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.
[<Fact>]
let ``An inconsistent system answers None`` () =
Assert.True((solveSystem ["x"; "y"] ["x + y - 1"; "x + y - 2"]).IsNone)

[<Fact>]
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)
23 changes: 23 additions & 0 deletions Sources/Wrappers/AngouriMath.FSharp/Functions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading