Packages
fixpoint
0.1.1
0.22.1
0.21.5
0.21.4
0.21.3
0.21.2
0.21.1
0.21.0
0.20.6
0.20.5
0.20.4
0.20.3
0.20.2
0.20.1
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.18.2
0.18.1
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.9
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.13.5
0.13.4
0.13.2
0.13.1
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.12.2
0.12.1
0.11.8
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.46
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.28
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.1.3
0.1.2
0.1.1
0.1.0
Constraint Programming Solver
Current section
Files
Jump to
Current section
Files
lib/solver/core/space.ex
defmodule CPSolver.Space do
@moduledoc """
Computation space.
The concept is taken from Chapter 12, "Concepts, Techniques, and Models
of Computer Programming" by Peter Van Roy and Seif Haridi.
"""
alias CPSolver.Utils
alias __MODULE__, as: Space
alias CPSolver.ConstraintStore, as: Store
alias CPSolver.Propagator, as: Propagator
alias CPSolver.Solution, as: Solution
alias CPSolver.IntVariable, as: Variable
alias CPSolver.DefaultDomain, as: Domain
require Logger
@behaviour :gen_statem
defstruct id: nil,
parent: nil,
keep_alive: false,
variables: [],
propagator_threads: %{},
store: Store.default_store(),
space: nil,
solver: nil,
solution_handler: nil,
search: nil,
opts: []
defp default_space_opts() do
[
store: Store.default_store(),
solution_handler: Solution.default_handler(),
search: CPSolver.Search.Strategy.default_strategy()
]
end
def create(variables, propagators, space_opts \\ [], gen_statem_opts \\ []) do
{:ok, _space} =
:gen_statem.start(
__MODULE__,
[
variables: variables,
propagators: propagators,
# Inject solver, if wasn't passed in opts
space_opts: inject_solver(space_opts)
],
gen_statem_opts
)
end
def stop(space) do
Process.alive?(space) && :gen_statem.stop(space)
end
defp inject_solver(space_opts) do
Keyword.put_new(space_opts, :solver, self())
end
def get_state_and_data(space) do
{_state, _data} = :sys.get_state(space)
end
def solution(%{variables: variables, space: space, store: store} = _data) do
Enum.reduce(variables, Map.new(), fn var, acc ->
Map.put(acc, var.id, store.get(space, var, :min))
end)
end
@impl true
def init(args) do
variables = Keyword.get(args, :variables)
propagators = Keyword.get(args, :propagators)
space_id = make_ref()
space_opts = Keyword.merge(default_space_opts(), Keyword.get(args, :space_opts, []))
store_impl = Keyword.get(space_opts, :store)
parent = Keyword.get(space_opts, :parent)
keep_alive = Keyword.get(space_opts, :keep_alive, false)
solution_handler = Keyword.get(space_opts, :solution_handler)
search_strategy = Keyword.get(space_opts, :search)
solver = Keyword.get(space_opts, :solver)
## Subscribe solver to space events
Utils.subscribe(solver, {:space, space_id})
{:ok, space_variables} = store_impl.create(self(), variables)
space_data = %Space{
id: space_id,
parent: parent,
keep_alive: keep_alive,
variables: space_variables,
store: store_impl,
opts: space_opts,
solution_handler: solution_handler,
search: search_strategy
}
{:ok, :start_propagation, space_data, [{:next_event, :internal, {:propagate, propagators}}]}
end
@impl true
def callback_mode() do
[:state_functions, :state_enter]
end
## Callbacks
def start_propagation(:enter, :start_propagation, data) do
{:keep_state, data}
end
def start_propagation(:internal, {:propagate, propagators}, data) do
propagator_threads = start_propagation(propagators)
{:next_state, :propagating, Map.put(data, :propagator_threads, propagator_threads)}
end
def propagating(:enter, :start_propagation, _data) do
:keep_state_and_data
end
def propagating(:info, {:stable, propagator_thread}, data) do
updated_data = set_propagator_stable(data, propagator_thread, true)
if stable?(updated_data) do
{:next_state, :stable, updated_data}
else
{:keep_state, updated_data}
end
end
def propagating(:info, {:running, propagator_thread}, data) do
Logger.debug("Running propagator #{inspect(propagator_thread)}")
{:keep_state, set_propagator_stable(data, propagator_thread, false)}
end
def propagating(:info, {:entailed, propagator_thread}, data) do
Logger.debug("Entailed propagator #{inspect(propagator_thread)}")
updated_data = update_entailed(data, propagator_thread)
if solved?(updated_data) do
{:next_state, :solved, updated_data}
else
{:keep_state, updated_data}
end
end
def propagating(:info, {:failed, _propagator_thread}, data) do
{:next_state, :failed, data}
end
def propagating(:info, :solved, data) do
{:next_state, :solved, data}
end
def failed(:enter, :propagating, data) do
handle_failure(data)
end
def solved(:enter, :propagating, data) do
handle_solved(data)
end
def stable(:enter, :propagating, data) do
handle_stable(data)
end
defp dispose(%{variables: variables, space: space, store: store} = _data) do
Enum.each(variables, fn var -> store.dispose(space, var) end)
end
defp start_propagation(propagators) do
Enum.reduce(propagators, Map.new(), fn p, acc ->
propagator_id = make_ref()
{:ok, thread} = Propagator.create_thread(self(), p, id: propagator_id)
Map.put(acc, propagator_id, %{thread: thread, propagator: p, stable: false})
end)
end
defp stable?(%{propagator_threads: threads} = _data) do
Enum.all?(threads, fn {_id, thread} -> thread.stable end)
end
defp set_propagator_stable(%{propagator_threads: threads} = data, propagator_id, stable?) do
%{
data
| propagator_threads:
Map.update!(threads, propagator_id, fn content -> Map.put(content, :stable, stable?) end)
}
end
def update_entailed(%{propagator_threads: threads} = data, propagator_thread) do
Map.put(
data,
:propagator_threads,
Map.delete(threads, propagator_thread)
|> tap(fn m -> Logger.debug("Active propagators: #{inspect(map_size(m))}") end)
)
end
defp solved?(data) do
map_size(data.propagator_threads) == 0
end
defp handle_failure(data) do
Logger.debug("The space #{inspect(data.id)} has failed")
publish(data, :failure)
shutdown(data, :failure)
end
defp handle_solved(%{solution_handler: solution_handler} = data) do
Logger.debug("The space #{inspect(data.id)} has been solved")
data
|> solution()
|> tap(fn solution -> publish(data, {:solution, solution}) end)
|> Solution.run_handler(solution_handler)
shutdown(data, :solved)
end
defp handle_stable(data) do
Logger.debug("Space #{inspect(data.id)} reports stable")
stop_propagators(data)
distribute(data)
end
defp stop_propagators(%{propagator_threads: threads} = _data) do
Enum.each(threads, fn {_id, thread} -> Propagator.dispose(thread) end)
end
def distribute(
%{
variables: variables,
store: store_impl,
space: space
} = data
) do
{variable_domains, all_fixed?} =
Enum.reduce_while(
variables,
{Map.new(), true},
fn v, {domains, fixed?} ->
case store_impl.domain(space, v) do
:fail ->
{:halt, {domains, :fail}}
d ->
domains = Map.put(domains, v.id, d)
{:cont, {domains, (Domain.fixed?(d) && fixed?) || false}}
end
end
)
case all_fixed? do
:fail -> handle_failure(data)
true -> handle_solved(data)
false -> do_distribute(data, variable_domains)
end
end
def do_distribute(
%{
variables: variables,
propagator_threads: threads,
search: search_strategy
} = data,
domains
) do
Logger.debug("Space #{inspect(data.id)} is distributing...")
var_to_branch_on = search_strategy.select_variable(variables)
var_domain = Map.get(domains, var_to_branch_on.id)
domain_partitions = search_strategy.partition(var_domain)
Enum.map(domain_partitions, fn partition ->
variable_copies =
Map.new(domains, fn {var_id, domain} ->
{var_id,
if var_id == var_to_branch_on.id do
Variable.new(partition)
else
Variable.new(domain)
end}
end)
propagator_copies =
Enum.map(threads, fn {_ref, thread} ->
{propagator_mod, args} = thread.propagator
## Replace variables in args to their copies
{propagator_mod,
Enum.map(args, fn
%CPSolver.Variable{id: id} = _arg ->
Map.get(variable_copies, id)
const ->
const
end)}
end)
{:ok, child_space} =
create(
Map.values(variable_copies),
propagator_copies,
Keyword.put(data.opts, :parent, data.id)
)
child_space
end)
|> tap(fn new_nodes ->
publish(data, {:nodes, new_nodes})
end)
shutdown(data, :stable)
end
defp publish(data, message) do
Utils.publish({:space, data.id}, message)
end
defp shutdown(%{keep_alive: keep_alive} = data, _reason) do
if !keep_alive do
dispose(data)
{:stop, :normal, data}
else
:keep_state_and_data
end
|> tap(fn _ -> publish(data, {:shutdown_space, self()}) end)
end
end