hdavid16 / DisjunctiveProgramming.jl

A JuMP extension for Generalized Disjunctive Programming
MIT License
27 stars 3 forks source link

Improve Printing for Disjunct/Logical Constraints and Disjunctions #100

Closed pulsipher closed 7 months ago

pulsipher commented 7 months ago

This closes #89.

Note that printing is different for Windows vs Linus/MacOS in that more unicode characters are used for the latter.

Let's demonstrate the printing using the model:

model = GDPModel()
@variable(model, x[1:2])
@variable(model, Y[1:2], Logical)

Disjunct Constraints

Here is what disjunct constraints look like for a Windows REPL.

julia> @constraint(model, c1, x[1]^2 >= 3.2, Disjunct(Y[1]))
c1 : x[1]² >= 3.2, if Y[1] = True

Now for a Linux REPL:

julia> @constraint(model, c1, x[1]^2 >= 3.2, Disjunct(Y[1]))
c1 : x[1]² ≥ 3.2, if Y[1] = True

Now in a Jupyter notebook for LaTeX printing: image

Disjunctions

Let's make a disjunction to demonstrate:

@constraint(model, 2x[1]^2 >= 1, Disjunct(Y[1]))
@constraint(model, x[2] - 1 == 2.1, Disjunct(Y[2]))
@constraint(model, 0 <= x[1] <= 1, Disjunct(Y[2]))
@disjunction(model, d1, Y)

Printing d1 in a Windows REPL we get:

d1 : [Y[1] --> {2 x[1]² >= 1}] or [Y[2] --> {x[2] == 3.1; x[1] in [0, 1]}]

In a Linux REPL we get:

d1 : [Y[1] ⟹ {2 x[1]² ≥ 1}] ⋁ [Y[2] ⟹ {x[2] = 3.1; x[1] ∈ [0, 1]}]

In Jupyter we get: image

Nested Disjunction

Now let's try a nested disjunction:

@variable(model, W[1:2], Logical)
@constraint(model, 2x[1]^2 >= 1, Disjunct(Y[1]))
@constraint(model, x[2] >= 2, Disjunct(W[1]))
@constraint(model, x[2] <= 3, Disjunct(W[2]))
@disjunction(model, inner, W, Disjunct(Y[1]))
@constraint(model, x[2] - 1 == 2.1, Disjunct(Y[2]))
@constraint(model, 0 <= x[1] <= 1, Disjunct(Y[2]))
@disjunction(model, d1, Y)

Printing d1 in a Windows REPL we get:

d1 : [Y[1] --> {2 x[1]² >= 1; [W[1] --> {x[2] >= 2}] or [W[2] --> {x[2] <= 3}]}] or [Y[2] --> {x[2] == 3.1; x[1] in [0, 1]}]

In a Linux REPL we get:

d1 : [Y[1] ⟹ {2 x[1]² ≥ 1; [W[1] ⟹ {x[2] ≥ 2}] ⋁ [W[2] ⟹ {x[2] ≤ 3}]}] ⋁ [Y[2] ⟹ {x[2] = 3.1; x[1] ∈ [0, 1]}]

In Jupyter we get: image

Logical Propositions

Let's try a logical proposition in a Windows REPL:

julia> @constraint(model, ¬(Y[1] && Y[2]) == (Y[1] || Y[2]) := true)
!(Y[1] and Y[2]) <--> (Y[1] or Y[2]) = True

Now in a Linux REPL:

julia> @constraint(model, ¬(Y[1] && Y[2]) == (Y[1] || Y[2]) := true)
¬(Y[1] ∧ Y[2]) ⟺ (Y[1] ∨ Y[2]) = True

Now in Jupyter: image Note that Y[1] is used instead of Y_{1}, this is a JuMP issue: https://github.com/jump-dev/JuMP.jl/issues/3604

Cardinality Constraints

Finally, let's look at cardinality constraints, starting with Windows (Linux is the same in this case):

julia> @constraint(model, Y in Exactly(1))
exactly(1, Y[1], Y[2])

Now in Jupyter: image

hdavid16 commented 7 months ago

This is just beautiful. What are your thoughts about changing the True/False to all lowercase to match the true/false notation in Julia?

pulsipher commented 7 months ago

The failures are because of very subtle error on Linux with replace that I cannot reproduce locally...

pulsipher commented 7 months ago

This is just beautiful. What are your thoughts about changing the True/False to all lowercase to match the true/false notation in Julia?

On one hand, lowercase would be consistent with Julia literals. On the other, uppercase would be consistent with the notation used in the GDP literature. In mind, printing should try to mimic the mathematical notation as close a possible.

hdavid16 commented 7 months ago

If the tests don't pass, maybe just disable the printing tests...

pulsipher commented 7 months ago

This appears to explain the problem: https://discourse.julialang.org/t/replacing-multiple-strings-errors/13654 It appears weird things happen with multiple arguments in replace when operating on a string.

hdavid16 commented 7 months ago

That thread is quite old. It should work. I use replace on strings with multiple arguments regularly on Julia 1.9.

codecov[bot] commented 7 months ago

Codecov Report

All modified and coverable lines are covered by tests :white_check_mark:

Comparison is base (ccceb26) 99.90% compared to head (84eeefc) 99.90%.

Additional details and impacted files ```diff @@ Coverage Diff @@ ## master #100 +/- ## ======================================= Coverage 99.90% 99.90% ======================================= Files 10 11 +1 Lines 1004 1078 +74 ======================================= + Hits 1003 1077 +74 Misses 1 1 ```

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

pulsipher commented 7 months ago

That thread is quite old. It should work. I use replace on strings with multiple arguments regularly on Julia 1.9.

It seems the issue was only partly fixed and only works on certain versions of Julia and computer OSs. Using only 1 argument at a time fixes the problem. Another weird Julia bug :)

hdavid16 commented 7 months ago

@pulsipher, I noticed that print(model) doesn't include the nice printing for disjunctions. What needs to be done in order for this to work?

pulsipher commented 7 months ago

@pulsipher, I noticed that print(model) doesn't include the nice printing for disjunctions. What needs to be done in order for this to work?

See #101.