Skip to content

Commit

Permalink
perf: improved equation solve performance and refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Casper Bollen authored and Casper Bollen committed Oct 24, 2023
1 parent 177399f commit 9bc6462
Show file tree
Hide file tree
Showing 8 changed files with 614 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
```
BenchmarkDotNet v0.13.9+228a464e8be6c580ad9408e98f18813f6407fb5a, macOS Sonoma 14.0 (23A344) [Darwin 23.0.0]
Apple M2 Max, 1 CPU, 12 logical and 12 physical cores
.NET SDK 6.0.415
[Host] : .NET 6.0.23 (6.0.2323.48002), Arm64 RyuJIT AdvSIMD DEBUG
DefaultJob : .NET 6.0.23 (6.0.2323.48002), Arm64 RyuJIT AdvSIMD
```
| Method | Mean | Error | StdDev |
|-------------------- |------------:|----------:|------------:|
| AllPairesInt_100 | 127.0 μs | 0.26 μs | 0.25 μs |
| AllPairsInt_200 | 751.4 μs | 3.80 μs | 3.55 μs |
| SolveCountMinIncl | 101.7 μs | 0.78 μs | 0.69 μs |
| Solve_1_Eqs_100 | 12,681.1 μs | 143.63 μs | 134.35 μs |
| Solve_1_Eqs_200 | 50,893.2 μs | 892.15 μs | 991.62 μs |
| Solve_3_Eqs_100 | 13,256.2 μs | 122.22 μs | 114.32 μs |
| Solve_3_Eqs_200 | 52,605.5 μs | 989.61 μs | 1,058.87 μs |
| Solve_1_Eqs_Rand_10 | 9,373.4 μs | 22.58 μs | 20.02 μs |
| Solve_1_Eqs_Rand_20 | 90,342.7 μs | 292.40 μs | 273.51 μs |
| Solve_3_Eqs_Rand_10 | 9,707.7 μs | 22.19 μs | 19.67 μs |
| Solve_3_Eqs_Rand_20 | 94,725.7 μs | 210.39 μs | 196.80 μs |
7 changes: 7 additions & 0 deletions benchmark/Equation.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Informedica.GenSolver.Lib

module Equation =

open Informedica.GenSolver.Lib


12 changes: 6 additions & 6 deletions benchmark/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ type ValueUnitBenchmarks () =

type EquationBenchmarks () =

let eqs_n n a b c d e f =
let eqs_takeN n a b c d e f =
let eqs =
[
"a = b + c"
Expand All @@ -331,15 +331,15 @@ type EquationBenchmarks () =
|> set "e" e
|> set "f" f

let eqs_1 = eqs_n 1 None None None None None None
let eqs_1 = eqs_takeN 1 None None None None None None

let eqs_1_max max =
let xs = [|1N..1N..max|]
eqs_n 1 None (Some xs) (Some xs) None None None
eqs_takeN 1 None (Some xs) (Some xs) None None None

let eqs_3_max max =
let xs = [|1N..1N..max|]
eqs_n 3 None (Some xs) (Some xs) None None None
eqs_takeN 3 None (Some xs) (Some xs) None None None

let eqs_1_max_100 = eqs_1_max 100N

Expand All @@ -354,11 +354,11 @@ type EquationBenchmarks () =

let eqs_1_Rand n =
let xs1, xs2 = Utils.getTwoRandomLists n 1_000
eqs_n 1 None (Some xs1) (Some xs2) None None None
eqs_takeN 1 None (Some xs1) (Some xs2) None None None

let eqs_3_Rand n =
let xs1, xs2 = Utils.getTwoRandomLists n 1_000
eqs_n 3 None (Some xs1) (Some xs2) None None None
eqs_takeN 3 None (Some xs1) (Some xs2) None None None


let eqs_1_rand_10 = eqs_1_Rand 10
Expand Down
1 change: 1 addition & 0 deletions benchmark/benchmark.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Equation.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
Expand Down
156 changes: 155 additions & 1 deletion src/Informedica.GenSolver.Lib/Equation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ module Equation =
/// Solve an equation **e**, return a list of
/// changed `Variable`s.
/// </summary>
let solve onlyMinIncrMax log eq =
let solve_old onlyMinIncrMax log eq =
// helper functions
let without x xs = xs |> List.filter (Variable.eqName x >> not)
let replAdd x xs = xs |> List.replaceOrAdd(Variable.eqName x) x
Expand Down Expand Up @@ -477,6 +477,160 @@ module Equation =
eq, Errored errs


let solve_ onlyMinIncrMax log eq =

let reorder = List.reorder >> List.mapi (fun i x -> (i, x))

let calc op1 op2 xs =
match xs with
| [] -> None
| [ x ] -> Some x
| y::xs ->
y |> op2 <| (xs |> List.reduce op1)
|> Some

if eq |> isSolved then eq, Unchanged
else
// log starting the equation solve
eq
|> Events.EquationStartedSolving
|> Logging.logInfo log

let (<==) = if onlyMinIncrMax then (@<-) else (^<-)
let vars, op1, op2 =
match eq with
| ProductEquation (y, xs) ->
if onlyMinIncrMax then
y::xs, (@*), (@/)
else
y::xs, (^*), (^/)
| SumEquation (y, xs) ->
if onlyMinIncrMax then
y::xs, (@+), (@-)
else
y::xs, (^+), (^-)

let vars = vars |> reorder

let calc vars =
vars
|> List.fold (fun acc vars ->
if acc |> Option.isSome then acc
else
match vars with
| _, []
| _, [ _ ] -> acc
| i, y::xs ->
let op2 = if i = 0 then op1 else op2
// log starting the calculation
(op1, op2, y, xs)
|> Events.EquationStartCalculation
|> Logging.logInfo log

xs
|> calc op1 op2
|> function
| None ->
// log finishing the calculation
(y::xs, false)
|> Events.EquationFinishedCalculation
|> Logging.logInfo log

None
| Some var ->
let yNew = y <== var

if yNew <> y then
// log finishing the calculation
([yNew], true)
|> Events.EquationFinishedCalculation
|> Logging.logInfo log

Some yNew
else
// log finishing the calculation
([], false)
|> Events.EquationFinishedCalculation
|> Logging.logInfo log

None
) None

let rec loop acc vars =
let vars =
vars
|> List.sortBy(fun (_, xs) ->
xs
|> List.tail
|> List.sumBy Variable.count
)

match calc vars with
| None -> acc, vars
| Some var ->
vars
|> List.map (fun (i, xs) ->
i,
xs |> List.replace (Variable.eqName var) var
)
|> List.sortBy(fun (_, xs) ->
xs
|> List.tail
|> List.sumBy Variable.count
)
|> loop (acc |> List.replaceOrAdd (Variable.eqName var) var)

vars
|> loop []
|> fun (c, vars) ->
if c |> List.isEmpty then eq, Unchanged
else
let c =
let vars = eq |> toVars
c
|> List.map (fun v2 ->
vars
|> List.tryFind (Variable.eqName v2)
|> function
| Some v1 ->
v2, v2.Values
|> Variable.ValueRange.diffWith v1.Values
| None ->
$"cannot find {v2}! in {vars}!"
|> failwith
)
|> List.filter (snd >> Set.isEmpty >> not)
|> Changed
let y, xs =
let vars = vars |> List.find (fst >> (=) 0) |> snd
vars |> List.head,
vars |> List.tail

(match eq with
| ProductEquation _ -> createProductEqExc (y, xs)
| SumEquation _ -> createSumEqExc (y, xs)
, c)
|> fun (eq, sr) ->
// log finishing equation solving
(eq, sr)
|> Events.EquationFinishedSolving
|> Logging.logInfo log

eq, sr


let solve onlyMinIncrMax log eq =
try
solve_ onlyMinIncrMax log eq
with
| Exceptions.SolverException errs ->
errs
|> List.iter (Logging.logError log)

eq, Errored errs



module Dto =

type VariableDto = Variable.Dto.Dto
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<None Include="Scripts\OrderEquations.fsx" />
<None Include="Scripts\Tests.fsx" />
<None Include="Scripts\Increment.fsx" />
<None Include="Scripts\Equation.fsx" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Informedica.Utils.Lib\Informedica.Utils.Lib.fsproj" />
Expand Down
Loading

0 comments on commit 9bc6462

Please sign in to comment.