# # Coins grid problem in Elixir. # # Problem from # Tony Hurlimann: "A coin puzzle - SVOR-contest 2007" # http://www.svor.ch/competitions/competition2007/AsroContestSolution.pdf # """ # In a quadratic grid (or a larger chessboard) with 31x31 cells, one # should place coins in such a way that the following conditions are # fulfilled: # 1. In each row exactly 14 coins must be placed. # 2. In each column exactly 14 coins must be placed. # 3. The sum of the quadratic horizontal distance from the main # diagonal of all cells containing a coin must be as small as possible. # 4. In each cell at most one coin can be placed. # # The description says to place 14x31 = 434 coins on the chessboard # each row containing 14 coins and each column also containing 14 coins. # """ # # Note: This problem is quite/very hard for (plain) CP solvers. A MIP solver solves # the 14,31 problem in millis. # # # Cf the MiniZinc model http://hakank.org/minizinc/coins_grid.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.CoinsGrid do alias Hakank.CPUtils alias CPSolver.IntVariable alias CPSolver.Constraint.Sum # alias CPSolver.Constraint.Equal # alias CPSolver.Constraint.AllDifferent.FWC, as: AllDifferent alias CPSolver.Model alias CPSolver.Objective # import CPSolver.Constraint.Factory import CPSolver.Variable.View.Factory require Logger def run(opts \\ []) do run(31, 14, opts) end def run_7_3(opts) do run(7, 3, opts) end def run(n, c, opts \\ []) do Logger.configure(level: :info) opts = Keyword.merge( default_opts() |> Keyword.put(:solution_handler, fn solution -> print_solution(solution, n, false) end), opts ) Logger.info("Started with n = #{n}, c = #{c}") # 7 ##n = # 31 # 3 ##c = ## 14 rs = 0..(n - 1) x = for i <- rs do for j <- rs do IntVariable.new(0..1, name: "x[#{i},#{j}]") end end x_flatten = List.flatten(x) # To be minimized z = IntVariable.new(0..(n * n * n), name: "z") # quadratic horizontal distance # MiniZinc: z = sum(i,j in 1..n) ( x[i,j]*(abs(i-j))*(abs(i-j)) ) sum_constraint = Sum.new( z, for i <- rs, j <- rs do mul(CPUtils.mat_at(x, i, j), abs(i - j) ** 2) end ) # MiniZinc: forall(i in 1..n) ( sum(j in 1..n) (x[i,j]) = c ) row_constraints = for row <- x do Sum.new(c, row) end # MiniZinc: forall(j in 1..n) ( sum(i in 1..n) (x[i,j]) = c ) col_constraints = for col <- CPUtils.transpose(x) do Sum.new(c, col) end # |> List.flatten constraints = [sum_constraint] ++ row_constraints ++ col_constraints # constraints = row_constraints ++ col_constraints model = Model.new( [z | x_flatten], constraints, # minimize z objective: Objective.minimize(z) ) {:ok, res} = CPSolver.solve( model, opts) IO.inspect(res.statistics) best_solution = List.last(res.solutions) if best_solution do print_solution(best_solution, n, true) else Logger.info("No solutions found") end end defp print_solution(solution, n, print_matrix?) do solution = Enum.map(solution, fn {_name, value} -> value value when is_integer(value) -> value end) coins = hd(solution) matrix = tl(solution) |> Enum.take(n * n) Logger.info("coins: #{coins}") print_matrix? && Enum.chunk_every(matrix, n) |> Enum.map(fn row -> Enum.join(row, " ") end) |> Enum.join("\n") |> then(fn matrix_str -> IO.puts("\nmatrix:\n" <> matrix_str <> "\n") end) end defp default_opts() do [ n: 31, c: 14, search: {:first_fail, :indomain_max}, #search: {:input_order, :indomain_max}, # search: {:most_constrained, :indomain_max}, # search: {:dom_deg, :indomain_max}, # search: {:first_fail, :indomain_min}, # search: {:max_regret, :indomain_max}, #search: {:first_fail, :indomain_random}, timeout: :timer.hours(1), # stop_on: {:max_solutions, 1}, ] end end