# # Assignment problems in Elixir. # # Different assignments problem, both minimization and maximization. # See the sources of the problem below. # Compare to the following MiniZinc models, from which these problems # are taken: # * http://www.hakank.org/minizinc/assignment.mzn # * http://www.hakank.org/minizinc/assignment2.mzn # * http://www.hakank.org/minizinc/assignment2_2.mzn # * http://www.hakank.org/minizinc/assignment3.mzn # * http://www.hakank.org/minizinc/assignment5.mzn # * http://www.hakank.org/minizinc/assignment6.mzn # This program was created by Hakan Kjellerstrand, hakank@gmail.com # See also my Elixir page: http://www.hakank.org/elxir/ # defmodule CPSolver.Examples.Hakank.Assignment do alias CPSolver.IntVariable # alias CPSolver.Constraint.AllDifferent.FWC, as: AllDifferent alias CPSolver.Constraint.Sum alias CPSolver.Constraint.LessOrEqual # alias CPSolver.Constraint.NotEqual # alias CPSolver.Constraint.Equal alias CPSolver.Model alias CPSolver.Objective import CPSolver.Constraint.Factory import CPSolver.Variable.View.Factory import Hakank.CPUtils # # Data from # Winston "Operations Research", Assignment Problems, page 393f # I added the fifth column # def problem(1) do op = :minimize cost = [[14, 5, 8, 7, 15], [ 2, 12, 6, 5, 3], [ 7, 8, 3, 9, 7], [ 2, 4, 6, 10, 1]] [op,cost] end # # Winston "Operations Research", page 398, swimming team example # (original version] # See http://www.hakank.org/minizinc/assignment2.mzn # def problem(2) do op = :minimize cost = [[54, 54, 51, 53], [51, 57, 52, 52], [50, 53, 54, 56], [56, 54, 55, 53]] [op,cost] end # # Winston "Operations Research", page 398, swimming team example # See http://www.hakank.org/minizinc/assignment2_2.mzn # expanded version # def problem(3) do op = :minimize cost = [[54, 54, 51, 53, 50,60,70,80,90,100], [51, 57, 52, 52, 40,50,60,70,80, 90], [50, 53, 54, 56, 40,50,60,80,93, 69], [56, 54, 55, 53, 60,80,40,60,50,100]] [op,cost] end # # Winston "Operations Research", page 399 # # """ # Tom Cruise, Freddy Prinze Jr, Harrison Ford, and Matt LeBlanc # are marooned on a desert island with Jennifer Anniston, # Courtney Cos, Gwynneth Paltrow, and Julia Roberts. # The 'compatibility matrix' in Table 52 indicate how much happiness # each couple would experience if the spend all their time toghether. # The happiness earned by a couple is proportional to the fraction # of time the spend toghether. # ... # The optimal solution requires that that each person send all their # time with one person of the opposite sex, so this result is often # referred to as the Marriage Theorem. # """ # # See http://www.hakank.org/minizinc/assignment3.mzn # males: # 1 "Tom Cruise" # 2 "Freddie Prinz Jr" # 3 "Harrison Ford" # 4 "Mark LeBlanc" # # females: # 1 "Jennifer Anniston" # 2 "Courtney Cox" # 3 "Gwynneth Paltrow" # 4 "Julia Roberts" def problem(4) do op = :maximize cost = [[7, 5, 8, 2], [7, 8, 9, 4], [3, 5, 7, 9], [5, 5, 6, 7]] [op,cost] end # From # "SAS OR 9.1 User's Guide Mathematical Programming" # """ # Consider assigning five programmers to five programming jobs. Each # programmer prefers specific programming job over others. [...] # Suppose you ask each programmer to rank the jobs according to preference # (using 1 for the most preferred job and 5 for the least preffered job]. # PROC ASSIGN maximizes the total preference of the group by minimizing the # sum of the preferences. # # PROGRAMMER JOB1 JOB2 JOB3 JOB4 JOB5 # PROGRAMMER1 4 1 3 5 2 # 2 2 1 3 4 5 # 3 3 2 4 1 5 # 4 2 3 4 5 1 # 5 4 2 3 1 5 # # """ # # See http://www.hakank.org/minizinc/assignment5.mzn # def problem(5) do op = :minimize cost = [[4, 1, 3, 5, 2], [2, 1, 3, 4, 5], [3, 2, 4, 1, 5], [2, 3, 4, 5, 1], [4, 2, 3, 1, 5]] [op,cost] end # # From GLPK:s example assign.mod: # """ # The assignment problem is one of the fundamental combinatorial # optimization problems. # # In its most general form, the problem is as follows: # # There are a number of agents and a number of tasks. Any agent can be # assigned to perform any task, incurring some cost that may vary # depending on the agent-task assignment. It is required to perform all # tasks by assigning exactly one agent to each task in such a way that # the total cost of the assignment is minimized. # # (From Wikipedia, the free encyclopedia.] # """ # # """ # These data correspond to an example from [Christofides]. # """ # # See http://www.hakank.org/minizinc/assignment6.mzn # ## Boris Okner: modified to sync with the latest API, ## change naming and result handling. ## def problem(6) do op = :minimize cost = [[13, 21, 20, 12, 8, 26, 22, 11], [12, 36, 25, 41, 40, 11, 4, 8], [35, 32, 13, 36, 26, 21, 13, 37], [34, 54, 7, 8, 12, 22, 11, 40], [21, 6, 45, 18, 24, 34, 12, 48], [42, 19, 39, 15, 14, 16, 28, 46], [16, 34, 38, 3, 34, 40, 22, 24], [26, 20, 5, 17, 45, 31, 37, 43]] [op,cost] end def run() do for p <- 1..6 do IO.puts("\nProblem #{p}") [op,cost] = problem(p) assignment(op, cost) end end def assignment(op, cost) do rows = length(cost) cols = length(Enum.at(cost,0)) # Who to assign to what task x = for i <- 0..rows-1 do for j <- 0..cols-1 do IntVariable.new(0..1, name: "x[#{i},#{j}]") end end x_flatten = x |> List.flatten total_cost = IntVariable.new(0..Enum.sum(cost |> List.flatten), name: "total_cost") # exacly one assignment per row, all rows must be assigned row_constraints = for i <- 0..rows-1 do Sum.new(1,for j <- 0..cols-1 do mat_at(x,i,j) end) end # zero or one assignments per column column_constraints = for j <- 0..cols-1 do {c_var,c_cons} = sum(for i <- 0..rows-1 do mat_at(x,i,j) end) less_cons = LessOrEqual.new(c_var,1) [c_cons,less_cons] end # calculate total_cost total_cost_constraint = Sum.new(total_cost, for i <- 0..rows-1, j <- 0..cols-1 do mul(mat_at(x,i,j),mat_at(cost,i,j)) end) model = Model.new(x_flatten ++ [total_cost], (row_constraints ++ column_constraints ++ [total_cost_constraint]) |> List.flatten, objective: if op == :minimize do Objective.minimize(total_cost) else Objective.maximize(total_cost) end ) 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 |> List.last total_cost_val = Enum.at(sol,rows*cols) IO.puts("total_cost: #{total_cost_val}") mat = Enum.take(sol,rows*cols) print_matrix(mat,rows,cols) for i <- 0..rows-1, j <- 0..cols-1, Enum.at(mat,i*cols+j) == 1 do j end |> IO.inspect(label: "assigned") end end