Packages
fixpoint
0.9.11
0.22.2
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/constraints/propagators/all_different/all_different_dc.ex
defmodule CPSolver.Propagator.AllDifferent.DC do
use CPSolver.Propagator
alias CPSolver.Algorithms.Kuhn
@moduledoc """
The domain-consistent propagator for AllDifferent constraint,
based on bipartite maximum matching.
"""
@impl true
def arguments(args) do
Arrays.new(args, implementation: Aja.Vector)
end
@impl true
def variables(args) do
Enum.map(args, fn x_el -> set_propagate_on(x_el, :domain_change) end)
end
@impl true
def filter(all_vars, state, changes) do
new_state =
(state && filter_impl(all_vars, state, changes)) ||
initial_state(all_vars)
(new_state == :resolved && :passive) ||
{:state, new_state}
end
defp filter_impl(
all_vars,
%{
sccs: sccs
} =
_state,
changes
) do
# initial_state(all_vars)
## Apply changes to affected SCCs
trigger_vars =
Map.keys(changes) |> MapSet.new()
Enum.reduce(sccs, [], fn %{component: component} = component_rec, sccs_acc ->
component_triggers = MapSet.intersection(trigger_vars, component)
if MapSet.size(component_triggers) == 0 do
[component_rec | sccs_acc]
else
update_component(all_vars, component_rec) ++ sccs_acc
end
end)
|> final_state()
end
defp update_component(
all_vars,
%{value_graph: value_graph, component: component} = component_rec
) do
## We will mostly do what initial reduction does, but on a (lesser) subset of variables
## that is represented by a component.
## TODO:
## The difference is that we'll only run maximum matching
## if any of the trigger variables doesn't have the value associated with the matching edge.
{component_variable_map, repair_matching?} = component_variables(component_rec, all_vars)
value_graph = update_value_graph(component_variable_map, value_graph)
## If matching hasn't changed, reuse;
## otherwise, start with partial matching built from fixed variables.
matching = (repair_matching? && %{}) || component_rec[:matching]
{_residual_graph, sccs} =
reduction(
all_vars,
value_graph,
Enum.map(component, fn var_id -> {:variable, var_id} end),
matching,
repair_matching?
)
sccs
end
## Pick out variables that match component's var ids;
## Also flags if there is any element in matching
## that does not have it's value in the domain of variable it previously matched.
defp component_variables(%{matching: matching} = _component_rec, vars) do
Enum.reduce(matching, {Map.new(), false}, fn {{:value, matching_value}, {:variable, var_id}},
{map_acc, matching_acc} ->
var = Propagator.arg_at(vars, var_id)
{
Map.put(map_acc, var_id, var),
## If match is still there, reuse it (will be in partial matching for the next step)
matching_acc || !contains?(var, matching_value)
}
end)
end
def initial_state(vars) do
{value_graph, variable_vertices, partial_matching} = build_value_graph(vars)
{_residual_graph, sccs} = reduction(vars, value_graph, variable_vertices, partial_matching)
final_state(sccs)
end
def final_state(sccs) do
(Enum.empty?(sccs) && :resolved) ||
%{
sccs: sccs
}
end
def reduction(vars, value_graph, variable_vertices, partial_matching, repair_matching? \\ true) do
maximum_matching =
(repair_matching? &&
compute_maximum_matching(value_graph, variable_vertices, partial_matching)) ||
partial_matching
{residual_graph, sccs} =
build_residual_graph(value_graph, maximum_matching)
|> reduce_residual_graph(vars)
{residual_graph, localize_state(sccs, value_graph, maximum_matching)}
end
def build_value_graph(var_list) when is_struct(var_list, Aja.Vector) do
Arrays.reduce(var_list, {0, Map.new()}, fn var, {idx_acc, map_acc} ->
{idx_acc + 1, Map.put(map_acc, idx_acc, var)}
end)
|> elem(1)
|> build_value_graph()
end
def build_value_graph(variable_map) when is_map(variable_map) do
Enum.reduce(
variable_map,
{Graph.new(), [], Map.new()},
fn {var_id, var}, {graph_acc, var_ids_acc, partial_matching_acc} ->
var_vertex = {:variable, var_id}
var_ids_acc = [var_vertex | var_ids_acc]
partial_matching_acc =
if fixed?(var) do
Map.put(partial_matching_acc, {:value, min(var)}, var_vertex)
else
partial_matching_acc
end
graph_acc =
Enum.reduce(domain_values(var), graph_acc, fn d, graph_acc2 ->
Graph.add_edge(graph_acc2, {:value, d}, var_vertex)
end)
{graph_acc, var_ids_acc, partial_matching_acc}
end
)
end
defp update_value_graph(variable_map, value_graph) do
Enum.reduce(variable_map, value_graph, fn {var_id, var}, graph_acc ->
variable_vertex = {:variable, var_id}
if Graph.in_degree(value_graph, variable_vertex) > size(var) do
## There are some edges to delete
Enum.reduce(Graph.in_edges(graph_acc, variable_vertex), graph_acc, fn %{v1: {:value, val}} =
edge,
g_acc2 ->
(contains?(var, val) && g_acc2) || Graph.delete_edge(g_acc2, edge.v1, edge.v2)
end)
else
graph_acc
end
end)
# Enum.reduce(triggers, value_graph, fn var_id, graph_acc ->
# var = Map.get(variable_map, var_id)
# var_vertex = {:variable, var_id}
# graph_values = Graph.in_neighbors(graph_acc, var_vertex)
# Enum.reduce(graph_values, graph_acc, fn {:value, val} = val_vertex, graph_acc2 ->
# contains?(var, val) && graph_acc2 || Graph.delete_edge(graph_acc2, val_vertex, var_vertex)
# end)
# end)
end
def compute_maximum_matching(value_graph, variable_ids, partial_matching) do
Kuhn.run(value_graph, variable_ids, partial_matching)
|> tap(fn matching -> map_size(matching) < length(variable_ids) && fail() end)
end
defp build_residual_graph(value_graph, maximum_matching) do
## The matching edges connect variables to values
Enum.reduce(
Graph.edges(value_graph),
value_graph,
fn %{
v1: {:value, _value} = v1,
v2: {:variable, _var_id} = v2
} = _edge,
residual_graph_acc ->
case Map.get(maximum_matching, v1) do
nil ->
## The vertices of unmatched values are connected to the sink vertex
Graph.add_edge(residual_graph_acc, :sink, v1)
## The edge is in matching - reverse
var when var == v2 ->
Graph.delete_edge(residual_graph_acc, v1, v2)
|> Graph.add_edge(v2, v1)
|> Graph.add_edge(v1, :sink)
_var ->
## For values in the domain, but not in matching, keep value -> variable edge
residual_graph_acc
end
end
)
end
defp reduce_residual_graph(residual_graph, vars) do
sccs = Graph.strong_components(residual_graph) |> sccs_to_sets()
residual_graph = remove_cross_edges(residual_graph, sccs, vars)
{residual_graph, postprocess_sccs(sccs)}
end
defp sccs_to_sets(sccs_arrays) do
Enum.map(sccs_arrays, fn component -> MapSet.new(component) |> MapSet.delete(:sink) end)
end
## Move parts of matching to where SCCs they belong to are
defp localize_state(sccs, value_graph, matching) do
## Matching is value => var_id map
## We want to reverse it, so we can do a lookup by var_id later on
matching_map =
Enum.reduce(matching, Map.new(), fn {{:value, value}, {:variable, var_id}}, map_acc ->
Map.put(map_acc, var_id, value)
end)
## Build records with list of variable ids and atached matching for SCCs
Enum.reduce(sccs, [], fn component, acc ->
if MapSet.size(component) <= 1 do
## We don't have to handle components with less than 2 variables
acc
else
{m, v_graph} =
Enum.reduce(component, {Map.new(), Graph.new()}, fn var_id,
{matching_acc, value_graph_acc} =
acc ->
case Map.get(matching_map, var_id) do
nil ->
acc
value ->
variable_vertex = {:variable, var_id}
value_edges = Graph.in_edges(value_graph, variable_vertex)
## We keep matching in the original form so we can reuse it
## in in the consequent iterations
{
Map.put(matching_acc, {:value, value}, variable_vertex),
Graph.add_edges(value_graph_acc, value_edges)
}
end
end)
[%{matching: m, component: component, value_graph: v_graph} | acc]
end
end)
end
defp remove_cross_edges(residual_graph, [_single_component] = _sccs, _vars) do
residual_graph
end
defp remove_cross_edges(residual_graph, sccs, vars) do
Enum.reduce(sccs, residual_graph, fn vertices, graph_acc ->
Enum.reduce(vertices, graph_acc, fn
{:variable, _var_id} = variable_vertex, graph_acc2 ->
edges = Graph.in_edges(graph_acc2, variable_vertex)
Enum.reduce(edges, graph_acc2, fn
%{v1: {:value, value} = value_vertex} = _edge, graph_acc3 ->
(value_vertex in vertices && graph_acc3) ||
Graph.delete_edge(graph_acc3, value_vertex, variable_vertex)
|> tap(fn _ ->
## If this is 'value -> variable' edge, remove the value from the domain of variable
maybe_remove_domain_value(value, variable_vertex, vars)
end)
_non_value_vertex, graph_acc3 ->
graph_acc3
end)
_non_variable_vertex, graph_acc2 ->
graph_acc2
end)
end)
end
defp maybe_remove_domain_value(value, {:variable, var_id}, vars) do
Propagator.arg_at(vars, var_id) |> remove(value)
end
defp maybe_remove_domain_value(_value, :sink, _vars) do
:ignore
end
defp postprocess_sccs(sccs) do
Enum.reduce(sccs, [], fn
## Drop single-element components
[_single], acc ->
acc
component, acc ->
## Turn non-singleton SCC to the set to speed up the lookups
## in consequent filtering calls.
## Drop 'value' vertices from SCC.
[
Enum.reduce(component, MapSet.new(), fn
{:variable, var_id} = _var_vertex, component_acc ->
MapSet.put(component_acc, var_id)
_non_var_vertex, map_acc ->
map_acc
end)
| acc
]
end)
end
defp fail() do
throw(:fail)
end
alias CPSolver.IntVariable, as: Variable
def test(domains) do
vars =
Enum.map(Enum.with_index(domains, 1), fn {d, idx} -> Variable.new(d, name: "x#{idx}") end)
{:ok, vars, _store} = CPSolver.ConstraintStore.create_store(vars)
build_value_graph(vars)
end
end