# # Set covering deployment in Elixir. # # From http://mathworld.wolfram.com/SetCoveringDeployment.html # """ # Set covering deployment (sometimes written "set-covering deployment" # and abbreviated SCDP for "set covering deployment problem") seeks # an optimal stationing of troops in a set of regions so that a # relatively small number of troop units can control a large # geographic region. ReVelle and Rosing (2000) first described # this in a study of Emperor Constantine the Great's mobile field # army placements to secure the Roman Empire. # """ # # Cf http://hakank.org/minizinc/set_covering_deployment.mzn # # This program was created by Hakan Kjellerstrand, hakank@gmail.com # See also my Elixir page: http://www.hakank.org/elxir/ # ## Boris Okner: modified to sync with the latest API, ## change naming and result handling. ## defmodule CPSolver.Examples.Hakank.SetCoveringDeployment do import Hakank.CPUtils alias CPSolver.IntVariable # alias CPSolver.Constraint.AllDifferent.FWC, as: AllDifferent # alias CPSolver.Constraint.Sum alias CPSolver.Constraint.LessOrEqual # alias CPSolver.Constraint.Circuit # alias CPSolver.Constraint.Element # alias CPSolver.Constraint.NotEqual # alias CPSolver.Constraint.Equal alias CPSolver.Model alias CPSolver.Objective # Defines: # add/2-3,element/2-3,element2d/3-4,mod/2-3, # subtract/2-3,sum/1-2 # (Note: add/2 is also defined in CPSolver.Variable.View.Factory) import CPSolver.Constraint.Factory # Defines: add/2,linear/3,minus/1,mul/2 # (Note: add/2 is also defined in CPSolver.Constraint.Factory) # import CPSolver.Variable.View.Factory # The connection matrix def problem(1) do [[0, 1, 0, 1, 0, 0, 1, 1], [1, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 0, 0], [1, 1, 0, 0, 0, 0, 1, 0], [0, 0, 1, 0, 0, 1, 1, 0], [0, 0, 1, 0, 1, 0, 1, 1], [1, 0, 0, 1, 1, 1, 0, 1], [1, 0, 0, 0, 0, 1, 1, 0]] end def run() do mat = problem(1) n = length(mat) # First army xs = for i <- 0..n-1 do IntVariable.new(0..1, name: "xs[#{i}]") end # Second army ys = for i <- 0..n-1 do IntVariable.new(0..1, name: "ys[#{i}]") end # # Constraint 1: There is always an army in a city (+ maybe a backup) # Or rather: Is there a backup, there must be an # an army # constraint1 = for {x,y} <- Enum.zip(xs,ys), do: LessOrEqual.new(y,x) # # Constraint 2: There should always be an backup army near # every city # constraint2 = for {x,row} <- Enum.zip(xs,mat) do {s_var, s_cons} = sum(for {m,y} <- Enum.zip(row,ys) do CPSolver.Variable.View.Factory.mul(y,m) end) {add_var,add_cons} = add(x,s_var) leq_cons = LessOrEqual.new(1,add_var) [s_cons,leq_cons,add_cons] end |> List.flatten # Thanks to Boris Okner for this neat solution. all_armies = xs ++ ys # objective: minimize the number of armies {sum_var, sum_constraint} = sum(all_armies) model = Model.new(all_armies, [sum_constraint | (constraint1 ++ constraint2) |> List.flatten ], objective: Objective.minimize(sum_var) ) Logger.configure(level: :info) opts = [ search: {:first_fail, :indomain_min}, # search: {:input_order, :indomain_min}, # search: {:first_fail, :indomain_random}, timeout: :infinity, # stop_on: {:max_solutions, 2}, ] {:ok, res} = CPSolver.solve(model, opts ) IO.inspect(res.statistics) sol = res.solutions |> hd # IO.inspect(sol, label: "sol") # x_val = Enum.slice(sol,0,n) # y_val = Enum.slice(sol,n,n) # z_val = Enum.at(sol,n*2) # IO.inspect(x_val, label: "x") # IO.inspect(y_val, label: "y") # IO.inspect(z_val, label: "z") IO.inspect(res.objective, label: "objective") xs_val = for i <- 0..n-1, do: get_solution_value(res,sol,"xs[#{i}]") ys_val = for i <- 0..n-1, do: get_solution_value(res,sol,"ys[#{i}]") IO.inspect(xs_val, label: "x") IO.inspect(ys_val, label: "y") # res end end