Current section

Files

Jump to
fixpoint lib solver space propagation.ex
Raw

lib/solver/space/propagation.ex

defmodule CPSolver.Space.Propagation do
alias CPSolver.Propagator.ConstraintGraph
alias CPSolver.Propagator
def run(propagators, store \\ nil)
def run(propagators, store) when is_list(propagators) do
propagators
|> Enum.map(fn p -> {make_ref(), p} end)
|> Map.new()
|> run(store)
end
def run(propagators, store) when is_map(propagators) and map_size(propagators) > 0 do
run(propagators, ConstraintGraph.create(propagators), store)
end
def run(propagators, constraint_graph, store) when is_map(propagators) do
propagators
|> run_impl(constraint_graph, store)
|> finalize(propagators)
end
defp run_impl(propagators, constraint_graph, _store) when map_size(propagators) == 0 do
constraint_graph
end
defp run_impl(propagators, constraint_graph, store) do
case propagate(propagators, store) do
:fail ->
:fail
changes when map_size(changes) == 0 ->
constraint_graph
changes ->
{reduced_graph, active_propagators} = wakeup(changes, constraint_graph)
run_impl(active_propagators, reduced_graph, store)
end
end
defp propagate(map, _store) when map_size(map) == 0 do
:no_changes
end
@spec propagate(map(), map()) :: :fail | :no_changes | {:changes, map()}
defp propagate(propagators, store) do
propagators
|> Task.async_stream(fn {_ref, p} -> Propagator.filter(p, store: store) end)
|> Enum.reduce_while(%{}, fn {:ok, res}, acc ->
case res do
{:changed, changes} ->
{:cont,
Map.merge(acc, changes, fn _var, prev_event, new_event ->
merge_events(prev_event, new_event)
end)}
:stable ->
{:cont, acc}
{:fail, _var} ->
{:halt, :fail}
end
end)
end
defp finalize(:fail, _propagators) do
:fail
end
## At this point, the space is either solved or stable.
## Reduce constraint graph and interpret the result.
defp finalize(%Graph{} = residue, propagators) do
(Graph.vertices(residue) == [] && :solved) ||
{:stable, residue, propagators_from_graph(residue, propagators)}
end
defp propagators_from_graph(graph, propagators) do
Enum.reduce(propagators, Map.new(), fn {p_id, p}, acc ->
(ConstraintGraph.get_propagator(graph, p_id) && Map.put(acc, p_id, p)) || acc
end)
end
## Wake up propagators based on the changes
defp wakeup(changes, graph) when is_map(changes) do
changes
|> Enum.reduce({graph, %{}}, fn {var_id, domain_change}, {g, propagators} ->
{maybe_remove_variable(g, var_id, domain_change),
Map.merge(
propagators,
Map.new(
ConstraintGraph.get_propagators(g, var_id, domain_change),
fn p -> {p.id, p} end
)
)}
end)
end
defp maybe_remove_variable(graph, var_id, :fixed) do
ConstraintGraph.remove_variable(graph, var_id)
end
defp maybe_remove_variable(graph, _var_id, _domain_change) do
graph
end
defp merge_events(prev_event, new_event) when prev_event == :fixed or new_event == :fixed do
:fixed
end
## TODO! Use hierarchy (i.e. fixed -> (min__change or max_change) -> bound_change -> domain_change)
defp merge_events(_prev_event, new_event) do
new_event
end
end