Current section

Files

Jump to
fixpoint lib solver core propagator constraint_graph.ex
Raw

lib/solver/core/propagator/constraint_graph.ex

defmodule CPSolver.Propagator.ConstraintGraph do
@moduledoc """
The constraint graph connects propagators and their variables.
The edge between a propagator and a variable represents a notification
the propagator receives upon varable's domain change.
"""
alias CPSolver.Propagator
alias CPSolver.Variable.Interface
alias CPSolver.DefaultDomain, as: Domain
@spec create([Propagator.t()] | %{reference() => Propagator.t()}) :: Graph.t()
def create(propagators) when is_list(propagators) do
propagators
|> Enum.map(fn p -> {p.id, p} end)
|> Map.new()
|> create()
end
def create(propagators) when is_map(propagators) do
Enum.reduce(propagators, Graph.new(), fn {propagator_id,
%{mod: propagator_mod, args: args} = p},
acc ->
args
|> propagator_mod.variables()
|> Enum.reduce(acc, fn var, acc2 ->
Graph.add_vertex(acc2, propagator_vertex(propagator_id))
|> Graph.add_edge({:variable, Interface.id(var)}, propagator_vertex(propagator_id),
label: get_propagate_on(var)
)
end)
|> Graph.label_vertex(propagator_vertex(propagator_id), p)
end)
end
def get_propagator_ids(constraint_graph, variable_id, filter_fun)
when is_function(filter_fun) do
constraint_graph
|> Graph.edges({:variable, variable_id})
|> Enum.flat_map(fn edge ->
if filter_fun.(edge) do
{:propagator, p_id} = edge.v2
[p_id]
else
[]
end
end)
end
## Get a list of propagator ids that "listen" to the domain change of given variable.
def get_propagator_ids(
constraint_graph,
variable_id,
domain_change
) do
get_propagator_ids(constraint_graph, variable_id, fn edge -> domain_change in edge.label end)
end
def has_variable?(graph, variable_id) do
Graph.has_vertex?(graph, {:variable, variable_id})
end
def get_propagator(%Graph{} = graph, propagator_id) do
case Graph.vertex_labels(graph, propagator_vertex(propagator_id)) do
[] -> nil
[p] -> p
end
end
def update_propagator(
%Graph{vertex_labels: labels, vertex_identifier: identifier} = graph,
propagator_id,
propagator
) do
vertex = propagator_vertex(propagator_id)
graph
|> Map.put(:vertex_labels, Map.put(labels, identifier.(vertex), [propagator]))
end
def propagator_vertex(propagator_id) do
{:propagator, propagator_id}
end
def remove_propagator(graph, propagator_id) do
remove_vertex(graph, propagator_vertex(propagator_id))
end
## Remove variable and all propagators that are isolated points as a result of variable removal
def remove_variable(graph, variable_id) do
remove_vertex(graph, {:variable, variable_id})
end
### Remove fixed variables and update propagators with variable domains.
### Returns updated graph and a list of propagators bound to variable domains
def update(graph, vars) do
## Remove fixed variables
{g1, propagators, variable_map} =
Enum.reduce(vars, {graph, [], Map.new()}, fn %{domain: domain} = v,
{graph_acc, propagators_acc, variables_acc} ->
{if Domain.fixed?(domain) do
remove_variable(graph_acc, v.id)
else
graph_acc
end, propagators_acc ++ get_propagator_ids(graph_acc, v.id, fn _ -> true end),
Map.put(variables_acc, v.id, v)}
end)
## Update domains
Enum.reduce(propagators, {g1, []}, fn p_id, {graph_acc, p_acc} ->
get_propagator(graph_acc, p_id)
|> Propagator.bind_to_variables(variable_map, :domain)
|> then(fn bound_p ->
{
update_propagator(graph_acc, p_id, bound_p),
[bound_p | p_acc]
}
end)
end)
end
def remove_vertex(graph, vertex) do
Graph.delete_vertex(graph, vertex)
end
defp get_propagate_on(variable) do
Map.get(variable, :propagate_on, Propagator.to_domain_events(:fixed))
end
end