diff --git a/Flips.Tests/Tests.fs b/Flips.Tests/Tests.fs index f96120f..401c0f3 100644 --- a/Flips.Tests/Tests.fs +++ b/Flips.Tests/Tests.fs @@ -255,6 +255,103 @@ module Types = LinearExpression.Empty.GetHashCode() |> ignore (LinearExpression.OfFloat 1.0).GetHashCode() |> ignore + [] + let ``Pretty print zero expression yields zero`` () = + let actual = LinearExpression.Zero.PrettyPrint() + Assert.Equal("0", actual) + + [] + let ``Pretty print positive constant expression yields constant without sign`` () = + let actual = 23.0 + LinearExpression.Zero |> _.PrettyPrint() + Assert.Equal("23", actual) + + [] + let ``Pretty print negative constant expression yields constant with minus`` () = + let actual = -23.0 + LinearExpression.Zero |> _.PrettyPrint() + Assert.Equal("-23", actual) + + [] + let ``Pretty print constant addition yields constants`` () = + let actual = 10.0 + 13.0 + LinearExpression.Zero |> _.PrettyPrint() + Assert.Equal("23", actual) + + [] + let ``Pretty print constant close to zero yields zero`` () = + let actual = 1e-8 + LinearExpression.Zero |> _.PrettyPrint() + Assert.Equal("0", actual) + + [] + 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) + + [] + 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) + + [] + 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) + + [] + 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) + + [] + 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) + + [] + 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 = + [] + 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) + + [] + 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) + + [] + 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) [ |] )>] module ModelTests = diff --git a/Flips/Types.fs b/Flips/Types.fs index bcc9ddb..d534622 100644 --- a/Flips/Types.fs +++ b/Flips/Types.fs @@ -1,4 +1,4 @@ -namespace Flips.Types +namespace Flips.Types open System.Collections.Generic open System @@ -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 [] 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 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index bffc84e..3308f1e 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -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