Skip to content

Commit

Permalink
Pretty printing for expressions (#206)
Browse files Browse the repository at this point in the history
* Pretty printing for linear expressions

* Pretty printing for constraint expressions

* Add release notes entry for pretty printing
  • Loading branch information
florenzen authored Sep 25, 2024
1 parent 4252db4 commit 0515a27
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 1 deletion.
97 changes: 97 additions & 0 deletions Flips.Tests/Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,103 @@ module Types =
LinearExpression.Empty.GetHashCode() |> ignore
(LinearExpression.OfFloat 1.0).GetHashCode() |> ignore

[<Fact>]
let ``Pretty print zero expression yields zero`` () =
let actual = LinearExpression.Zero.PrettyPrint()
Assert.Equal("0", actual)

[<Fact>]
let ``Pretty print positive constant expression yields constant without sign`` () =
let actual = 23.0 + LinearExpression.Zero |> _.PrettyPrint()
Assert.Equal("23", actual)

[<Fact>]
let ``Pretty print negative constant expression yields constant with minus`` () =
let actual = -23.0 + LinearExpression.Zero |> _.PrettyPrint()
Assert.Equal("-23", actual)

[<Fact>]
let ``Pretty print constant addition yields constants`` () =
let actual = 10.0 + 13.0 + LinearExpression.Zero |> _.PrettyPrint()
Assert.Equal("23", actual)

[<Fact>]
let ``Pretty print constant close to zero yields zero`` () =
let actual = 1e-8 + LinearExpression.Zero |> _.PrettyPrint()
Assert.Equal("0", actual)

[<Fact>]
let ``Pretty print linear combination with constant yields normalized expression`` () =
let x = Decision.createContinuous "x" 0.0 1.0
let y = Decision.createContinuous "y" 0.0 1.0
let z = Decision.createContinuous "z" 0.0 1.0
let actual = 8.0 * x + 7.0 * y - 2.0 * z + 25.0 |> _.PrettyPrint()
Assert.Equal("25 + 8 * x + 7 * y - 2 * z", actual)

[<Fact>]
let ``Pretty print linear combination without constants yields normalized expression`` () =
let x = Decision.createContinuous "x" 0.0 1.0
let y = Decision.createContinuous "y" 0.0 1.0
let z = Decision.createContinuous "z" 0.0 1.0
let actual = 8.0 * x + 7.0 * y - 2.0 * z |> _.PrettyPrint()
Assert.Equal("8 * x + 7 * y - 2 * z", actual)

[<Fact>]
let ``Pretty print linear combination with leading negative coefficient yields normalized expression`` () =
let x = Decision.createContinuous "x" 0.0 1.0
let y = Decision.createContinuous "y" 0.0 1.0
let z = Decision.createContinuous "z" 0.0 1.0
let actual = -8.0 * x + 7.0 * y - 2.0 * z |> _.PrettyPrint()
Assert.Equal("-8 * x + 7 * y - 2 * z", actual)

[<Fact>]
let ``Pretty print linear combination with leading minus one coefficient omits one`` () =
let x = Decision.createContinuous "x" 0.0 1.0
let y = Decision.createContinuous "y" 0.0 1.0
let z = Decision.createContinuous "z" 0.0 1.0
let actual = -1.0 * x + 7.0 * y - 2.0 * z |> _.PrettyPrint()
Assert.Equal("-x + 7 * y - 2 * z", actual)

[<Fact>]
let ``Pretty print linear combination with middle coefficient one omits one`` () =
let x = Decision.createContinuous "x" 0.0 1.0
let y = Decision.createContinuous "y" 0.0 1.0
let z = Decision.createContinuous "z" 0.0 1.0
let actual = -8.0 * x + 1.0 * y - 2.0 * z |> _.PrettyPrint()
Assert.Equal("-8 * x + y - 2 * z", actual)

[<Fact>]
let ``Pretty print linear combination with coefficient close to one omits one`` () =
let x = Decision.createContinuous "x" 0.0 1.0
let y = Decision.createContinuous "y" 0.0 1.0
let z = Decision.createContinuous "z" 0.0 1.0
let actual = -8.0 * x + 1.0000001 * y - 2.0 * z |> _.PrettyPrint()
Assert.Equal("-8 * x + y - 2 * z", actual)

module ConstraintExpression =
[<Fact>]
let ``Pretty print equality uses equal sign`` () =
let x = Decision.createContinuous "x" 0.0 1.0
let y = Decision.createContinuous "y" 0.0 1.0
let z = Decision.createContinuous "z" 0.0 1.0
let actual = -2.0 + y == x + 2.0 * z + 10.0 |> _.PrettyPrint()
Assert.Equal("-2 + y = 10 + x + 2 * z", actual)

[<Fact>]
let ``Pretty print greater-or-equality uses greater-or-equal sign`` () =
let x = Decision.createContinuous "x" 0.0 1.0
let y = Decision.createContinuous "y" 0.0 1.0
let z = Decision.createContinuous "z" 0.0 1.0
let actual = -2.0 + y >== x + 2.0 * z + 10.0 |> _.PrettyPrint()
Assert.Equal("-2 + y >= 10 + x + 2 * z", actual)

[<Fact>]
let ``Pretty print less-or-equality uses less-or-equal sign`` () =
let x = Decision.createContinuous "x" 0.0 1.0
let y = Decision.createContinuous "y" 0.0 1.0
let z = Decision.createContinuous "z" 0.0 1.0
let actual = -2.0 + y <== x + 2.0 * z + 10.0 |> _.PrettyPrint()
Assert.Equal("-2 + y <= 10 + x + 2 * z", actual)

[<Properties(Arbitrary = [| typeof<Types> |] )>]
module ModelTests =
Expand Down
44 changes: 43 additions & 1 deletion Flips/Types.fs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Flips.Types
namespace Flips.Types

open System.Collections.Generic
open System
Expand Down Expand Up @@ -349,12 +349,54 @@ and
static member (>==) (lhs:LinearExpression, rhs:LinearExpression): ConstraintExpression =
Inequality (lhs, GreaterOrEqual, rhs)

member this.PrettyPrint() : string =
let reduced = LinearExpression.Reduce this

let ppCoefficient (x: double) =
let c =
if Math.Abs x - 1.0 < 10e-4 then ""
else if x < 0 then (-x).ToString() + " * "
else x.ToString() + " * "

if x < 0 then " - " + c else " + " + c

let ppSummands =
[ for (DecisionName decision, coefficient) in (reduced.Coefficients |> Map.ofDictionary |> Map.toList) ->
ppCoefficient coefficient + decision ]

let ppConstant =
if Math.Abs reduced.Offset < 10e-4 then
"0"
else
reduced.Offset.ToString()

let includedConstant =
if ppSummands.Length = 0 then ppConstant
elif ppConstant <> "0" then ppConstant
else ""

String.concat "" (includedConstant :: ppSummands)
|> fun output ->
if output.StartsWith(" + ") then output[3..]
else if output.StartsWith(" - ") then "-" + output[3..]
else output

and
/// The representation of how two LinearExpressions must relate to one another
[<NoComparison>]
ConstraintExpression =
| Inequality of LHS:LinearExpression * Inequality * RHS:LinearExpression
| Equality of LHS:LinearExpression * RHS:LinearExpression
member this.PrettyPrint() : string =
match this with
| Inequality(lhs, relation, rhs) ->
let ppRelation =
match relation with
| GreaterOrEqual -> ">="
| LessOrEqual -> "<="

lhs.PrettyPrint() + " " + ppRelation + " " + rhs.PrettyPrint()
| Equality(lhs, rhs) -> lhs.PrettyPrint() + " = " + rhs.PrettyPrint()

/// A unique identified for a Constraint
type ConstraintName = ConstraintName of string
Expand Down
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Remove x64 restriction
* Change to FAKE build project
* Update to latest Google.OrTools package
* Add methods to pretty print linear and constraint expressions

### 2.4.9 - Wednesday, October 19th, 2022
* Fix problem with Units of Measure when multiplying float and LinearExpression
Expand Down

0 comments on commit 0515a27

Please sign in to comment.