lib/examples/hakank/set_covering_deployment.ex

#
# 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},
      space_threads: 12,
      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