Packages
fixpoint
0.11.6
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/utils/value_graph.ex
defmodule CPSolver.ValueGraph do
alias CPSolver.Utils
alias CPSolver.Variable.Interface
alias CPSolver.Propagator
def build(variables, opts \\ []) do
## Builds value graph and supporting structures
## that may be used further (for instance, by Kuhn algorithm).
## Value graph is bipartite.
## Value graph edges are {:variable, id} -> {:value, value}
##
## Fixed matching is a map {:variable, id} => {:value, value}
## , where variable is fixed.
## The set of {:variable, id} elements is a "variable" partition of value graph,
## that is, vertices that represent variables.
## Optional:
## :check_matching (false by default) - fails if there is no perfect matching
## that is, some variables fixed to the same value.
## Note: we do not explicitly add edges, they will be derived through
## BitGraph's `neighbor_finder` function based on current variables' domain values.
##
check_matching? = Keyword.get(opts, :check_matching, false)
{value_vertices, var_count, fixed, _fixed_values} =
Enum.reduce(variables, {MapSet.new(), 0, Map.new(), MapSet.new()}, fn var,
{
vertices_acc,
var_count_acc,
fixed_matching_acc,
fixed_values_acc
} ->
domain = Utils.domain_values(var)
domain_size = MapSet.size(domain)
vertices_acc =
Enum.reduce(domain, vertices_acc, fn value, acc ->
MapSet.put(acc, {:value, value})
end)
{fixed_matching_acc, fixed_values_acc} =
if domain_size == 1 do
fixed_value = Enum.fetch!(domain, 0)
MapSet.member?(fixed_values_acc, fixed_value) && check_matching? && fail()
{
Map.put(fixed_matching_acc, {:variable, var_count_acc}, {:value, fixed_value}),
MapSet.put(fixed_values_acc, fixed_value)
}
else
{fixed_matching_acc, fixed_values_acc}
end
{vertices_acc, var_count_acc + 1, fixed_matching_acc, fixed_values_acc}
end)
%{
graph:
BitGraph.new(num_vertices: MapSet.size(value_vertices) + var_count)
|> then(fn g ->
Enum.reduce(0..(var_count - 1), g, fn idx, g_acc ->
BitGraph.add_vertex(g_acc, {:variable, idx})
end)
end)
|> BitGraph.add_vertices(value_vertices),
left_partition: MapSet.new(0..(var_count - 1), fn idx -> {:variable, idx} end),
fixed: fixed
}
end
defp fail(reason \\ :fail) do
throw(reason)
end
def default_neighbor_finder(variables) do
fn graph, vertex_index, direction ->
vertex = BitGraph.V.get_vertex(graph, vertex_index)
get_neighbors(graph, vertex, variables, direction)
end
end
defp get_neighbors(_graph, {:variable, _var_index}, _variables, :in) do
MapSet.new([])
end
defp get_neighbors(_graph, {:value, _value}, _variables, :out) do
MapSet.new([])
end
defp get_neighbors(graph, {:variable, var_index}, variables, :out) do
Propagator.arg_at(variables, var_index)
|> Utils.domain_values()
|> Enum.reduce(MapSet.new(), fn value, acc ->
MapSet.put(acc, BitGraph.V.get_vertex_index(graph, {:value, value}))
end)
end
defp get_neighbors(graph, {:value, value}, variables, :in) do
Enum.reduce(variables, {0, MapSet.new()}, fn var, {idx, n_acc} ->
{idx + 1,
(Interface.contains?(var, value) &&
MapSet.put(n_acc, BitGraph.V.get_vertex_index(graph, {:variable, idx}))) || n_acc}
end)
|> elem(1)
end
## Matching edges will be reversed
def matching_neighbor_finder(graph, variables, matching) do
default_neighbor_finder = default_neighbor_finder(variables)
{indexed_matching, reversed_indexed_matching} =
Enum.reduce(matching, {Map.new(), Map.new()}, fn {{:variable, var_index} = var_vertex,
{:value, value} = value_vertex},
{matching_acc, reverse_matching_acc} ->
propagator_variable = Propagator.arg_at(variables, var_index)
Interface.contains?(propagator_variable, value) ||
fail({:invalid_matching, var_vertex, value_vertex})
var_vertex_index = BitGraph.V.get_vertex_index(graph, var_vertex)
value_vertex_index = BitGraph.V.get_vertex_index(graph, value_vertex)
{
Map.put(
matching_acc,
var_vertex_index,
{value_vertex_index, propagator_variable, value, var_vertex}
),
# value_vertex_index),
Map.put(
reverse_matching_acc,
value_vertex_index,
{var_vertex_index, propagator_variable, value, var_vertex}
)
# var_vertex_index)
}
end)
fn graph, vertex_index, direction ->
neighbors = default_neighbor_finder.(graph, vertex_index, direction)
adjust_neighbors(
neighbors,
vertex_index,
indexed_matching,
reversed_indexed_matching,
direction
)
end
end
## Out-neighbors
## If vertex is a 'variable', remove matched value from 'out' neighbors.
##
## If vertex is a 'value', make matched variable a single 'out' neighbor.
## Otherwise, keep neighbors as is.
##
defp adjust_neighbors(
neighbors,
vertex_index,
indexed_matching,
reversed_indexed_matching,
:out
) do
case Map.get(indexed_matching, vertex_index) do
nil ->
case Map.get(reversed_indexed_matching, vertex_index) do
nil ->
neighbors
{variable_match, variable, matching_value, variable_vertex} ->
(Interface.contains?(variable, matching_value) &&
MapSet.new([variable_match])) ||
fail({:invalid_matching, variable_vertex, {:value, matching_value}})
end
{value_match, _, _, _} ->
## Remove value from 'out' neighbors of variable vertex
MapSet.delete(neighbors, value_match)
end
end
## In-neighbors
## If vertex is a 'variable', make matched value a single 'in' neighbor.
##
## If vertex is a 'value', remove matched variable from 'in' neighbors.
##
defp adjust_neighbors(neighbors, vertex_index, indexed_matching, reversed_indexed_matching, :in) do
case Map.get(reversed_indexed_matching, vertex_index) do
nil ->
case Map.get(indexed_matching, vertex_index) do
## All variables have to have a matched value (unlikely failure!)
nil ->
fail(:unmatched_variable)
{value_match, variable, matching_value, variable_vertex} ->
(Interface.contains?(variable, matching_value) &&
MapSet.new([value_match])) ||
fail({:invalid_matching, variable_vertex, {:value, matching_value}})
end
{variable_match, _, _, _} ->
MapSet.delete(neighbors, variable_match)
end
end
end