Current section

Files

Jump to
fixpoint lib examples hakank quasigroup_completion.ex
Raw

lib/examples/hakank/quasigroup_completion.ex

#
# Quasigroup Completion in Elixir.
#
# See
# Carla P. Gomes and David Shmoys:
# "Completing Quasigroups or Latin Squares: Structured Graph Coloring Problem"
#
# See also
# Ivars Peterson "Completing Latin Squares"
# http://www.maa.org/mathland/mathtrek_5_8_00.html
# """
# Using only the numbers 1, 2, 3, and 4, arrange four sets of these
# numbers into a four-by-four array so that no column or row contains
# the same two numbers. The result is known as a Latin square.
# ...
# The so-called quasigroup completion problem concerns a table that is
# correctly but only partially filled in. The question is whether the
# remaining blanks in the table can be filled in to obtain a complete
# Latin square (or a proper quasigroup multiplication table).
# """
#
#
# This program was created by Hakan Kjellerstrand, hakank@gmail.com
# See also my Elixir page: http://www.hakank.org/elxir/
#
defmodule QuasigroupCompletion do
alias CPSolver.IntVariable
# alias CPSolver.Constraint.Sum
# alias CPSolver.Constraint.Equal
# alias CPSolver.Constraint.AllDifferent.FWC, as: AllDifferent
alias CPSolver.Model
#
# Example from Ruben Martins and Inès Lynce
# Breaking Local Symmetries in Quasigroup Completion Problems, page 3
# The solution is unique:
# 1 3 2 5 4
# 2 5 4 1 3
# 4 1 3 2 5
# 5 4 1 3 2
# 3 2 5 4 1
#
def puzzle(1) do
# 0 are the unknowns
[[1, 0, 0, 0, 4], [0, 5, 0, 0, 0], [4, 0, 0, 2, 0], [0, 4, 0, 0, 0], [0, 0, 5, 0, 1]]
end
#
# Example from Gomes & Shmoys, page 3.
# Solution:
# 4 1 2 3
# 2 3 4 1
# 1 4 3 2
# 3 2 1 4
#
def puzzle(2) do
[[0, 1, 2, 3], [2, 0, 4, 1], [1, 4, 0, 2], [3, 0, 1, 0]]
end
# Example from Gomes & Shmoys, page 7
# Two solutions.
#
def puzzle(3) do
[[0, 1, 0, 0], [0, 0, 2, 0], [0, 3, 0, 0], [0, 0, 0, 4]]
end
#
# Example from Global Constraint Catalogue
# http://www.emn.fr/x-info/sdemasse/gccat/sec2.7.108.html
#
# 12 solutions.
#
def puzzle(4) do
[[1, 0, 0, 0], [0, 0, 0, 3], [3, 0, 0, 0], [0, 0, 0, 1]]
end
#
# Problem from http://www.cs.cornell.edu/gomes/QUASIdemo.html
# (n = 10]
# Pattern #1.
# There are _many_ solutions to this problem.
#
def puzzle(5) do
[
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 2, 0, 0, 0, 0],
[1, 0, 0, 0, 2, 0, 0, 0, 0, 0],
[0, 0, 0, 2, 1, 0, 0, 0, 0, 0],
[0, 0, 2, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 2],
[0, 0, 0, 0, 0, 0, 0, 0, 2, 0],
[0, 0, 0, 0, 0, 0, 0, 2, 0, 0]
]
end
#
# Problem from http://www.cs.cornell.edu/gomes/QUASIdemo.html
# (n = 10]
# Pattern #2.
# There are many solutions to this problem.
#
def puzzle(6) do
[
[0, 0, 1, 2, 3, 4, 0, 0, 0, 0],
[0, 1, 2, 3, 0, 0, 4, 0, 0, 0],
[1, 2, 3, 0, 0, 0, 0, 4, 0, 0],
[2, 3, 0, 0, 0, 0, 0, 0, 4, 0],
[3, 0, 0, 0, 0, 0, 0, 0, 0, 4],
[5, 6, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 5, 6, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 5, 6, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 5, 6, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 5, 6, 0, 0, 0, 0]
]
end
#
# Problem from http://www.cs.cornell.edu/gomes/QUASIdemo.html
# (n = 10]
# Pattern #3.
# Coding:
# dark red = 1
# light blue = 2
# dark blue = 3
# light red = 4
# brown = 5
# green = 6
# pink = 7
# grey = 8
# black = 9
# yellow = 10
# There are 40944 solutions for this pattern.
#
# This takes 9.5s, about to solve and print all solutions.
#
def puzzle(7) do
[
[0, 0, 1, 5, 2, 6, 7, 8, 0, 0],
[0, 1, 5, 2, 0, 0, 6, 7, 8, 0],
[1, 5, 2, 0, 0, 0, 0, 6, 7, 8],
[5, 2, 0, 0, 0, 0, 0, 0, 6, 7],
[2, 0, 0, 0, 0, 0, 0, 0, 0, 6],
[4, 10, 0, 0, 0, 0, 0, 0, 3, 9],
[0, 4, 10, 0, 0, 0, 0, 3, 9, 0],
[0, 0, 4, 10, 0, 0, 3, 9, 0, 0],
[0, 0, 0, 4, 10, 3, 9, 0, 0, 0],
[0, 0, 0, 0, 4, 9, 0, 0, 0, 0]
]
end
#
# Problem from http://www.cs.cornell.edu/gomes/QUASIdemo.html
# (n = 10]
# Pattern #4.
# dark red = 1
# light blue = 2
# dark blue = 3
# light red = 4
# Note: There are no solutions to this problem.
#
def puzzle(8) do
[
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[2, 1, 0, 0, 0, 0, 0, 0, 0, 4],
[3, 2, 1, 0, 0, 0, 0, 0, 4, 0],
[0, 3, 2, 1, 0, 0, 0, 4, 0, 0],
[0, 0, 3, 2, 1, 0, 4, 0, 0, 0],
[0, 0, 0, 3, 2, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 3, 2, 1, 0, 0, 0],
[0, 0, 0, 4, 0, 3, 2, 1, 0, 0],
[0, 0, 4, 0, 0, 0, 3, 2, 1, 0],
[0, 4, 0, 0, 0, 0, 0, 3, 2, 1]
]
end
#
# Problem from http://www.cs.cornell.edu/gomes/QUASIdemo.html
# (n = 10]
# Pattern #5
# Note: There are no solutions to this problem.
#
def puzzle(9) do
[
[0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 2, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
end
#
#
#
def main() do
# Problem 5..7 yields a huge number of solutions.
# Let's just pick the first.
1..7
|> Enum.map(fn p ->
IO.puts("Running problem #{p}")
cond do
p in 5..7 -> quasigroup_completion(puzzle(p), 1)
true -> quasigroup_completion(puzzle(p))
end
end)
end
#
# Problem5
# Generating all 40944 solutions takes a long time
#
def main2() do
quasigroup_completion(puzzle(5))
end
#
# Running problem 8 (no solution)
# Takes long time in current Fixpoint version
def main3() do
quasigroup_completion(puzzle(8))
end
#
# Running problem 9 (no solution)
# Takes long time in current Fixpoint version
def main4() do
quasigroup_completion(puzzle(9))
end
@doc """
quasigroup_completion(mat,num_sols \\ :infinity)
Solves the Quasigroup completion problem for the matrix `mat`.
`num_sols` are the required number of solutions, defaults to :infinity.
"""
def quasigroup_completion(mat, _num_sols \\ :infinity) do
n = length(mat)
dom = 1..n
#
# Decision variables
#
x =
for i <- 0..(n - 1) do
for j <- 0..(n - 1) do
v = CPUtils.mat_at(mat, i, j)
if v > 0 do
# > 0: this is a hint
IntVariable.new(v, name: "x[#{i},#{j}]")
else
# 0: unknown
IntVariable.new(dom, name: "x[#{i},#{j}]")
end
end
end
x_flatten = List.flatten(x)
#
# Constraints
#
constraints = CPUtils.latin_square(x)
model =
Model.new(
x_flatten,
constraints
)
Logger.configure(level: :info)
{:ok, result} =
CPSolver.solve_sync(model,
search: {:input_order, :indomain_random},
# search: {:first_fail, :indomain_min},
# search: {:first_fail, :indomain_random},
# search: {:input_order, :indomain_max},
stop_on: {:max_solutions, 1},
timeout: :timer.hours(8),
space_threads: 4
)
IO.inspect(result.statistics)
result.solutions
# |> Enum.map(fn s -> print_matrix(s,n,n,"~3w") end)
# IO.inspect(result.statistics)
end
end