Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pretty printing for expressions #206

Merged
merged 3 commits into from
Sep 25, 2024
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
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
Loading