Packages
fixpoint
0.12.1
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
alias CPSolver.Propagator.Variable, as: PropagatorVariable
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)
ignore_fixed_variables? = Keyword.get(opts, :ignore_fixed_variables, 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
)
left_partition =
Enum.reduce(0..(var_count - 1), MapSet.new(), fn idx, acc ->
variable_vertex = {:variable, idx}
(ignore_fixed_variables? && Map.has_key?(fixed, variable_vertex) && acc) ||
MapSet.put(acc, variable_vertex)
end)
value_vertices =
(ignore_fixed_variables? &&
MapSet.reject(value_vertices, fn {:value, value} -> value in fixed_values end)) ||
value_vertices
%{
graph:
BitGraph.new(
num_vertices: MapSet.size(value_vertices) + var_count,
neighbor_finder: default_neighbor_finder(variables)
)
|> BitGraph.add_vertices(left_partition)
|> BitGraph.add_vertices(value_vertices),
left_partition: left_partition,
fixed_matching: #!ignore_fixed_variables? &&
fixed,
fixed_values: fixed_values
}
end
## Forward checking (cascading removal of fixed variables).
## Note: value graph with default neighbor finder
## has edges oriented from variables to values.
## The result of forward checking will be a value graph with
## removed fixed variable vertices, and the side effect will be
## a domain reduction such that no domain value is shared between fixed variables.
def forward_checking(graph, fixed_vertices, variables) do
{updated_graph, _, newly_fixed_vertices} = forward_checking_impl(graph, fixed_vertices, variables)
%{graph: updated_graph, new_fixed: newly_fixed_vertices}
end
defp forward_checking_impl(graph, fixed_vertices, variables) do
forward_checking_impl(graph, fixed_vertices, variables, MapSet.new())
end
defp forward_checking_impl(graph, fixed_vertices, variables, newly_fixed) do
for var_vertex <- fixed_vertices, reduce: {graph, MapSet.new(), newly_fixed} do
{graph_acc, fixed_acc, newly_fixed_acc} = _acc ->
value_vertex = BitGraph.out_neighbors(graph, var_vertex) |> MapSet.to_list() |> hd
graph = BitGraph.delete_vertex(graph_acc, var_vertex)
{updated_graph, new_fixed_vertices} =
Enum.reduce(BitGraph.in_neighbors(graph, value_vertex), {graph, fixed_acc}, fn {:variable, _var_index} =
var_neighbor,
{g_acc, f_acc} ->
%{graph: g_acc, change: change} =
delete_edge(g_acc, var_neighbor, value_vertex, variables)
f_acc =
case change do
:fixed ->
MapSet.put(f_acc, var_neighbor)
_domain_change ->
f_acc
end
{g_acc, f_acc}
end)
forward_checking_impl(
BitGraph.delete_vertex(updated_graph, value_vertex), new_fixed_vertices, variables, MapSet.union(newly_fixed_acc, new_fixed_vertices))
end
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)
(vertex && get_neighbors(graph, vertex, variables, direction)) || MapSet.new()
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
get_variable(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 = get_variable(variables, var_index)
Interface.contains?(propagator_variable, value) || MapSet.new()
# 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}
),
Map.put(
reverse_matching_acc,
value_vertex_index,
{var_vertex_index, propagator_variable, value, var_vertex}
)
}
end)
fn graph, vertex_index, direction ->
neighbors = default_neighbor_finder.(graph, vertex_index, direction)
## By construction, 'variable' vertex indices go first
{vertex_type, vertex_matching} =
(vertex_index <= map_size(indexed_matching) && {:variable, indexed_matching}) ||
{:value, reversed_indexed_matching}
adjust_neighbors(
neighbors,
vertex_index,
vertex_type,
vertex_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,
:variable,
vertex_matching,
:out
) do
case Map.get(vertex_matching, vertex_index) do
nil ->
## variables must have matching value assigned
# fail({:invalid_matching, {:variable_not_matched, vertex_index}})
## Revised: we could use matching that ignores already fixed variable
MapSet.new()
{value_match, _, _, _} ->
## Remove value from 'out' neighbors of variable vertex
MapSet.delete(neighbors, value_match)
end
end
defp adjust_neighbors(
neighbors,
vertex_index,
:value,
vertex_matching,
:out
) do
case Map.get(vertex_matching, vertex_index) do
nil ->
neighbors
{variable_match, variable, matching_value, _variable_vertex} ->
## matched value must be in the domain of matching variable
(Interface.contains?(variable, matching_value) &&
MapSet.new([variable_match])) || MapSet.new()
# fail(
# {:invalid_matching,
# variable_vertex, {:value, matching_value}}
# )
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, :variable, vertex_matching, :in) do
case Map.get(vertex_matching, vertex_index) do
nil ->
## variables must have matching value assigned
## fail({:invalid_matching, {:variable_not_matched, vertex_index}})
## Revised: we may want to use matching that ignores fixed variables
MapSet.new()
{value_match, variable, matching_value, _variable_vertex} ->
(Interface.contains?(variable, matching_value) &&
MapSet.new([value_match])) || MapSet.new()
# fail(
# {:invalid_matching,
# variable_vertex, value_match}
# )
end
end
defp adjust_neighbors(neighbors, vertex_index, :value, vertex_matching, :in) do
case Map.get(vertex_matching, vertex_index) do
nil ->
## Nowhere in matching; must be a free value
neighbors
{variable_match, _, _, _} ->
MapSet.delete(neighbors, variable_match)
end
end
def delete_edge(
graph,
{:value, _value} = value_vertex,
{:variable, _var_index} = var_vertex,
variables
) do
delete_edge(graph, var_vertex, value_vertex, variables)
end
def delete_edge(graph, {:variable, var_index}, {:value, value} = value_vertex, variables) do
propagator_variable = get_variable(variables, var_index)
change = PropagatorVariable.remove(propagator_variable, value)
%{
graph:
(BitGraph.degree(graph, value_vertex) == 0 &&
BitGraph.delete_vertex(graph, value_vertex)) || graph,
change: change
}
end
defp get_variable(variables, var_index) do
Propagator.arg_at(variables, var_index)
end
end