From 6a536fdf148ebfce1210aa32704176e9a9ee088c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 2 Sep 2024 10:33:04 +0200 Subject: [PATCH 1/7] Allow using MiniZincSet from JuMP --- src/MiniZinc.jl | 9 +++++ src/write.jl | 19 +++++---- test/examples/packing.jl | 84 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 test/examples/packing.jl diff --git a/src/MiniZinc.jl b/src/MiniZinc.jl index 625fa04..c98cd47 100644 --- a/src/MiniZinc.jl +++ b/src/MiniZinc.jl @@ -15,11 +15,20 @@ const ReifiedEqualTo{T} = MOI.Reified{MOI.EqualTo{T}} const ReifiedBinPacking{T} = MOI.Reified{MOI.BinPacking{T}} const ReifiedTable{T} = MOI.Reified{MOI.Table{T}} +struct MiniZincSet <: MOI.AbstractVectorSet + name::String + fields::Vector{Union{Int,UnitRange{Int}}} +end + +MOI.dimension(set::MiniZincSet) = maximum(maximum, set.fields) +Base.copy(set::MiniZincSet) = set + MOI.Utilities.@model( Model, (MOI.ZeroOne, MOI.Integer, MOI.EqualTo{Bool}), (MOI.EqualTo, MOI.GreaterThan, MOI.LessThan, MOI.Interval), ( + MiniZincSet, MOI.AllDifferent, MOI.Circuit, MOI.CountAtLeast, diff --git a/src/write.jl b/src/write.jl index 61f5982..88ab631 100644 --- a/src/write.jl +++ b/src/write.jl @@ -149,9 +149,17 @@ function _to_string( return ret end -struct MiniZincSet <: MOI.AbstractSet - name::String - fields::Vector{Union{Int,UnitRange{Int}}} +function _write_constraint( + io::IO, + predicates::Set, + variables::Dict, + f::MOI.VectorOfVariables, + mzn::MiniZincSet, +) + strs = [_to_string(variables, f.variables[field]) for field in mzn.fields] + println(io, "constraint $(mzn.name)(", join(strs, ", "), ");") + push!(predicates, mzn.name) + return end function _write_constraint( @@ -167,10 +175,7 @@ function _write_constraint( MOI.Circuit, }, ) - mzn = MiniZincSet(s) - strs = [_to_string(variables, f.variables[field]) for field in mzn.fields] - println(io, "constraint $(mzn.name)(", join(strs, ", "), ");") - push!(predicates, mzn.name) + _write_constraint(io, predicates, variables, f, MiniZincSet(s)) return end diff --git a/test/examples/packing.jl b/test/examples/packing.jl new file mode 100644 index 0000000..d122022 --- /dev/null +++ b/test/examples/packing.jl @@ -0,0 +1,84 @@ +# Inspired from the square packing tutorial in https://www.minizinc.org/ +function test_packing() + # Number of squares + n = 6 + # Size of each square + sizes = collect(1:n) + upper_bound = sum(sizes) + + model = MOI.instantiate( + () -> MiniZinc.Optimizer{Int}("chuffed"); + with_cache_type = Int, + with_bridge_type = Int, + ) + + # We need this `s` variable that is trivially equal to `sizes` + # because `MiniZincSet` only does not take constants + s = [MOI.add_constrained_variable(model, MOI.Integer())[1] for i in 1:n] + x = [MOI.add_constrained_variable(model, MOI.Integer())[1] for i in 1:n] + y = [MOI.add_constrained_variable(model, MOI.Integer())[1] for i in 1:n] + max_x, _ = MOI.add_constrained_variable(model, MOI.Integer()) + max_y, _ = MOI.add_constrained_variable(model, MOI.Integer()) + + for i in 1:n + MOI.add_constraint( + model, + s[i], + MOI.EqualTo(sizes[i]), + ) + MOI.add_constraint( + model, + x[i], + MOI.Interval(1, upper_bound), + ) + MOI.add_constraint( + model, + y[i], + MOI.Interval(1, upper_bound), + ) + end + MOI.add_constraint( + model, + max_x, + MOI.Interval(1, upper_bound), + ) + MOI.add_constraint( + model, + max_y, + MOI.Interval(1, upper_bound), + ) + + for i in 1:n + MOI.add_constraint( + model, + 1max_x - 1x[i], + MOI.GreaterThan(sizes[i]), + ) + MOI.add_constraint( + model, + 1max_y - 1y[i], + MOI.GreaterThan(sizes[i]), + ) + end + + MOI.add_constraint( + model, + [x; y; s; s], + MiniZinc.MiniZincSet( + "diffn", + [1:n, n .+ (1:n), 2n .+ (1:n), 3n .+ (1:n)], + ), + ) + + MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) + obj = (1max_x) * max_y + MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj) + + MOI.optimize!(model) + + @test MOI.get(model, MOI.TerminationStatus()) === MOI.OPTIMAL + @test MOI.get(model, MOI.PrimalStatus()) === MOI.FEASIBLE_POINT + @test MOI.get(model, MOI.ResultCount()) == 1 + @test MOI.get(model, MOI.ObjectiveValue()) == 120 + return +end From 87da4df865927e602177395f0987552ead4618e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 2 Sep 2024 11:51:35 +0200 Subject: [PATCH 2/7] Fix format --- test/examples/packing.jl | 42 +++++++--------------------------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/test/examples/packing.jl b/test/examples/packing.jl index d122022..3c1bd18 100644 --- a/test/examples/packing.jl +++ b/test/examples/packing.jl @@ -21,44 +21,16 @@ function test_packing() max_y, _ = MOI.add_constrained_variable(model, MOI.Integer()) for i in 1:n - MOI.add_constraint( - model, - s[i], - MOI.EqualTo(sizes[i]), - ) - MOI.add_constraint( - model, - x[i], - MOI.Interval(1, upper_bound), - ) - MOI.add_constraint( - model, - y[i], - MOI.Interval(1, upper_bound), - ) + MOI.add_constraint(model, s[i], MOI.EqualTo(sizes[i])) + MOI.add_constraint(model, x[i], MOI.Interval(1, upper_bound)) + MOI.add_constraint(model, y[i], MOI.Interval(1, upper_bound)) end - MOI.add_constraint( - model, - max_x, - MOI.Interval(1, upper_bound), - ) - MOI.add_constraint( - model, - max_y, - MOI.Interval(1, upper_bound), - ) + MOI.add_constraint(model, max_x, MOI.Interval(1, upper_bound)) + MOI.add_constraint(model, max_y, MOI.Interval(1, upper_bound)) for i in 1:n - MOI.add_constraint( - model, - 1max_x - 1x[i], - MOI.GreaterThan(sizes[i]), - ) - MOI.add_constraint( - model, - 1max_y - 1y[i], - MOI.GreaterThan(sizes[i]), - ) + MOI.add_constraint(model, 1max_x - 1x[i], MOI.GreaterThan(sizes[i])) + MOI.add_constraint(model, 1max_y - 1y[i], MOI.GreaterThan(sizes[i])) end MOI.add_constraint( From af9cd859bf9a9c9ec134db0c516e8acd3793da73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 2 Sep 2024 14:20:57 +0200 Subject: [PATCH 3/7] Add test --- test/runtests.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/runtests.jl b/test/runtests.jl index a4502a6..96d6515 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1696,6 +1696,14 @@ function test_integer_bounds_less_than_than() index_map, _ = MOI.optimize!(mzn, model) @test MOI.get(mzn, MOI.TerminationStatus()) == MOI.OPTIMAL @test MOI.get(mzn, MOI.VariablePrimal(), index_map[x]) == 1 +end + +function test_minizincset() + set = MiniZinc.MiniZincSet("diffn", [1:2, 3:4, 5:6, 7:8]) + @test MOI.dimension(set) == 8 + model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()) + x, _ = MOI.add_constrained_variables(model, set) + @test length(x) == 8 return end From f869e0afd9cc45596f6a3efee04737dc775db285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Legat?= Date: Mon, 2 Sep 2024 14:56:30 +0200 Subject: [PATCH 4/7] Use test.mzn --- test/examples/packing.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/examples/packing.jl b/test/examples/packing.jl index 3c1bd18..04b8bda 100644 --- a/test/examples/packing.jl +++ b/test/examples/packing.jl @@ -11,6 +11,7 @@ function test_packing() with_cache_type = Int, with_bridge_type = Int, ) + MOI.set(model, MOI.RawOptimizerAttribute("model_filename"), "test.mzn") # We need this `s` variable that is trivially equal to `sizes` # because `MiniZincSet` only does not take constants @@ -52,5 +53,6 @@ function test_packing() @test MOI.get(model, MOI.PrimalStatus()) === MOI.FEASIBLE_POINT @test MOI.get(model, MOI.ResultCount()) == 1 @test MOI.get(model, MOI.ObjectiveValue()) == 120 + rm("test.mzn") return end From d571efa826c41b8932b031baf11ea8efe94ac35b Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 3 Sep 2024 09:03:41 +1200 Subject: [PATCH 5/7] Update test/examples/packing.jl --- test/examples/packing.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/examples/packing.jl b/test/examples/packing.jl index 04b8bda..6481d4e 100644 --- a/test/examples/packing.jl +++ b/test/examples/packing.jl @@ -1,3 +1,8 @@ +# Copyright (c) 2022 MiniZinc.jl contributors +# +# Use of this source code is governed by an MIT-style license that can be found +# in the LICENSE.md file or at https://opensource.org/licenses/MIT. + # Inspired from the square packing tutorial in https://www.minizinc.org/ function test_packing() # Number of squares From 1f471556d6fca69c04dd5b7f95394e605c9e85ae Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 3 Sep 2024 09:12:37 +1200 Subject: [PATCH 6/7] Update test/examples/packing.jl --- test/examples/packing.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/examples/packing.jl b/test/examples/packing.jl index 6481d4e..4955560 100644 --- a/test/examples/packing.jl +++ b/test/examples/packing.jl @@ -19,7 +19,7 @@ function test_packing() MOI.set(model, MOI.RawOptimizerAttribute("model_filename"), "test.mzn") # We need this `s` variable that is trivially equal to `sizes` - # because `MiniZincSet` only does not take constants + # because `MiniZincSet` supports only VectorOfVariables s = [MOI.add_constrained_variable(model, MOI.Integer())[1] for i in 1:n] x = [MOI.add_constrained_variable(model, MOI.Integer())[1] for i in 1:n] y = [MOI.add_constrained_variable(model, MOI.Integer())[1] for i in 1:n] From 69c73a03d6f8d31f996e0d0dda75d2ec6062bd41 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Tue, 3 Sep 2024 09:15:34 +1200 Subject: [PATCH 7/7] Update packing.jl --- test/examples/packing.jl | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/test/examples/packing.jl b/test/examples/packing.jl index 4955560..cb664c1 100644 --- a/test/examples/packing.jl +++ b/test/examples/packing.jl @@ -5,19 +5,15 @@ # Inspired from the square packing tutorial in https://www.minizinc.org/ function test_packing() - # Number of squares n = 6 - # Size of each square sizes = collect(1:n) upper_bound = sum(sizes) - model = MOI.instantiate( () -> MiniZinc.Optimizer{Int}("chuffed"); with_cache_type = Int, with_bridge_type = Int, ) MOI.set(model, MOI.RawOptimizerAttribute("model_filename"), "test.mzn") - # We need this `s` variable that is trivially equal to `sizes` # because `MiniZincSet` supports only VectorOfVariables s = [MOI.add_constrained_variable(model, MOI.Integer())[1] for i in 1:n] @@ -25,35 +21,25 @@ function test_packing() y = [MOI.add_constrained_variable(model, MOI.Integer())[1] for i in 1:n] max_x, _ = MOI.add_constrained_variable(model, MOI.Integer()) max_y, _ = MOI.add_constrained_variable(model, MOI.Integer()) - - for i in 1:n - MOI.add_constraint(model, s[i], MOI.EqualTo(sizes[i])) - MOI.add_constraint(model, x[i], MOI.Interval(1, upper_bound)) - MOI.add_constraint(model, y[i], MOI.Interval(1, upper_bound)) - end + MOI.add_constraint.(model, s, MOI.EqualTo.(sizes)) + MOI.add_constraint.(model, x, MOI.Interval(1, upper_bound)) + MOI.add_constraint.(model, y, MOI.Interval(1, upper_bound)) MOI.add_constraint(model, max_x, MOI.Interval(1, upper_bound)) MOI.add_constraint(model, max_y, MOI.Interval(1, upper_bound)) - - for i in 1:n - MOI.add_constraint(model, 1max_x - 1x[i], MOI.GreaterThan(sizes[i])) - MOI.add_constraint(model, 1max_y - 1y[i], MOI.GreaterThan(sizes[i])) - end - + MOI.add_constraint.(model, 1max_x .- 1x, MOI.GreaterThan.(sizes)) + MOI.add_constraint.(model, 1max_y .- 1y, MOI.GreaterThan.(sizes)) MOI.add_constraint( model, - [x; y; s; s], + MOI.VectorOfVariables([x; y; s; s]), MiniZinc.MiniZincSet( "diffn", [1:n, n .+ (1:n), 2n .+ (1:n), 3n .+ (1:n)], ), ) - MOI.set(model, MOI.ObjectiveSense(), MOI.MIN_SENSE) obj = (1max_x) * max_y MOI.set(model, MOI.ObjectiveFunction{typeof(obj)}(), obj) - MOI.optimize!(model) - @test MOI.get(model, MOI.TerminationStatus()) === MOI.OPTIMAL @test MOI.get(model, MOI.PrimalStatus()) === MOI.FEASIBLE_POINT @test MOI.get(model, MOI.ResultCount()) == 1