Packages
fixpoint
0.1.0
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
require Logger
@behaviour :gen_statem
defstruct id: nil,
parent: nil,
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
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 = Process.alias()
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)
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,
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
Logger.debug("The space has been solved")
shutdown(data, :solved)
end
def failed(:enter, :propagating, data) do
handle_failure(data)
shutdown(data, :failure)
end
def solved(:enter, :propagating, data) do
handle_solved(data)
shutdown(data, :solved)
end
def stable(:enter, :propagating, data) do
handle_stable(data)
shutdown(
data
|> distribute()
|> Enum.map(fn child_data ->
{:ok, child_space} =
create(
child_data.variables,
child_data.propagators,
Keyword.put(data.opts, :parent, data.id)
)
child_space
end)
|> tap(fn new_nodes ->
publish(data, {:nodes, new_nodes})
end)
|> then(fn children -> Map.put(data, :children, children) end),
:distribute
)
end
defp dispose(
%{variables: variables, propagator_threads: threads, space: space, store: store} = _data
) do
Enum.each(variables, fn var -> store.dispose(space, var) end)
Enum.each(threads, fn {id, thread} -> Propagator.dispose(thread) 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))
end
defp solved?(data) do
map_size(data.propagator_threads) == 0
end
defp handle_failure(data) do
Logger.debug("The space has failed")
publish(data, :failure)
end
defp handle_solved(%{solution_handler: solution_handler} = data) do
data
|> solution()
|> tap(fn solution -> publish(data, {:solution, solution}) end)
|> Solution.run_handler(solution_handler)
end
defp handle_stable(data) do
Logger.debug("Space #{inspect(data.id)} is stable")
end
def distribute(
%{
variables: variables,
propagator_threads: threads,
store: store_impl,
search: search_strategy,
space: space
} = _data
) do
variable_domains =
Map.new(
variables,
fn v -> {v.id, store_impl.domain(space, v)} end
)
var_to_branch_on = search_strategy.select_variable(variables)
var_domain = Map.get(variable_domains, var_to_branch_on.id)
domain_partitions = search_strategy.partition(var_domain)
Enum.map(domain_partitions, fn partition ->
variable_copies =
Map.new(variable_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)
%{variables: Map.values(variable_copies), propagators: propagator_copies}
end)
end
defp publish(data, message) do
Utils.publish({:space, data.id}, message)
end
defp shutdown(data, _reason) do
publish(data, {:shutdown_space, data.space})
dispose(data)
{:stop, :normal, data}
end
end