Packages
fixpoint
0.14.4
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/variables/value_graph.ex
defmodule CPSolver.ValueGraph do
alias CPSolver.Utils
alias CPSolver.Variable.Interface
alias CPSolver.Propagator
alias CPSolver.Propagator.Variable, as: PropagatorVariable
alias Iter.Iterable.{Empty, Mapper, FlatMapper, Filterer}
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
%{
value_graph:
BitGraph.new(
max_vertices: MapSet.size(value_vertices) + var_count,
allocate_adjacency_table?: false,
neighbor_finder: default_neighbor_finder(variables),
variable_count: var_count
)
|> BitGraph.add_vertices(left_partition)
|> BitGraph.add_vertices(value_vertices),
left_partition: left_partition,
fixed_matching: fixed,
fixed_values: fixed_values,
unfixed_indices: Enum.reduce(left_partition,
MapSet.new(), fn {:variable, idx}, acc ->
Map.has_key?(fixed, {:variable, idx}) && acc || MapSet.put(acc, idx) end)
}
end
defp fail(reason \\ :fail) do
throw(reason)
end
def get_variable_count(value_graph) do
get_in(value_graph, [:opts, :variable_count])
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)) || Empty.new()
end
end
defp get_neighbors(_graph, {:variable, _var_index}, _variables, :in) do
Empty.new()
end
defp get_neighbors(_graph, {:value, _value}, _variables, :out) do
Empty.new()
end
defp get_neighbors(graph, {:variable, var_index}, variables, :out) do
get_variable(variables, var_index)
|> Interface.iterator()
|> Mapper.new(fn value ->
BitGraph.V.get_vertex_index(graph, {:value, value})
end)
end
defp get_neighbors(graph, {:value, value}, variables, :in) do
FlatMapper.new(0..get_variable_count(graph) - 1,
fn idx ->
Interface.contains?(get_variable(variables, idx), value) &&
[BitGraph.V.get_vertex_index(graph, {:variable, idx})] || []
end
)
end
defp get_neighbors(_graph, _additional_vertex, _variables, _direction) do
Empty.new()
end
## Matching edges will be reversed
def matching_neighbor_finder(graph, variables, matching, _free_nodes) do
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)
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 ->
## By construction, 'variable' vertex indices go first
vertex_type =
(vertex_index <= get_variable_count(graph) && :variable) ||
:value
adjust_to_matching(
graph,
neighbor_finder,
vertex_index,
vertex_type,
direction,
indexed_matching,
reversed_indexed_matching
)
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_to_matching(
graph,
neighbor_finder,
vertex_index,
:variable,
:out,
variable_matching,
_value_matching
) do
case Map.get(variable_matching, vertex_index) do
nil ->
Empty.new()
{value_match, _, _, _} ->
## Remove value from 'out' neighbors of variable vertex
Filterer.new(neighbor_finder.(graph, vertex_index, :out), fn value -> value != value_match end)
end
end
defp adjust_to_matching(
_graph,
_neighbor_finder,
vertex_index,
:value,
:out,
_variable_matching,
value_matching
) do
case Map.get(value_matching, vertex_index) do
nil ->
Empty.new()
{variable_match, _variable, _matching_value, _variable_vertex} ->
[variable_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_to_matching(
_graph,
_neighbor_finder,
vertex_index,
:variable,
:in,
variable_matching,
_value_matching
) do
case Map.get(variable_matching, vertex_index) do
nil ->
## Variable outside matching
Empty.new()
{value_match, _variable, _matching_value, _variable_vertex} ->
## Matching value is the only in-neighbor
[value_match]
end
end
defp adjust_to_matching(
graph,
neighbor_finder,
vertex_index,
:value,
:in,
variable_matching,
value_matching
) do
neighbors = neighbor_finder.(graph, vertex_index, :in)
Filterer.new(neighbors, fn var_neighbor ->
## Exclude the variable that matches the value
## (this would represent an 'out' edge from the value to variable, as opposed to 'in' edge)
case Map.get(value_matching, vertex_index) do
nil -> true
{variable_match, _, _, _} ->
variable_match != var_neighbor
end
## All in-edges from variables have to be in matching.
## This makes sure that there will be no variable outside
## of the subgraph defined by the matching.
&& Map.has_key?(variable_matching, var_neighbor)
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)
(BitGraph.isolated_vertex?(graph, value_vertex) &&
BitGraph.delete_vertex(graph, value_vertex)) || graph
end
def get_variable(variables, var_index) do
Propagator.arg_at(variables, var_index)
end
end