defmodule CPSolver.Propagator.AllDifferent.Utils do
alias CPSolver.ValueGraph
alias CPSolver.Variable.Interface
alias BitGraph.Neighbor, as: N
## Splits graph into SCCs,
## and removes cross-edges.
## `vertices` is a subset of graph vertices
## that DFS will be run on.
## This means the split will be made on parts of the graph that
## are reachable from these vertices.
## `remove_edge_fun/3` is a function
## fn(graph, from_vertex, to_vertex)
## that returns (possibly modified) graph.
##
## Returns tuple {sccs, reduced_graph}
def split_to_sccs(graph, vertices, remove_edge_fun \\ fn graph, from, to -> BitGraph.delete_edge(graph, from, to) end) do
BitGraph.Algorithms.strong_components(graph,
vertices: vertices,
component_handler:
{fn component, acc -> scc_component_handler(component, remove_edge_fun, acc) end,
{MapSet.new(), graph}},
algorithm: :tarjan
)
end
def scc_component_handler(component, remove_edge_fun, {component_acc, graph_acc} = _current_acc) do
{variable_vertices, updated_graph} =
Enum.reduce(component, {MapSet.new(), graph_acc}, fn vertex_index,
{vertices_acc, g_acc} = acc ->
case BitGraph.V.get_vertex(graph_acc, vertex_index) do
## We only need to remove out-edges from 'variable' vertices
## that cross to other SCCS
{:variable, variable_id} = variable_vertex ->
cross_neighbors = BitGraph.V.out_neighbors(graph_acc, vertex_index)
{
MapSet.put(vertices_acc, variable_id),
remove_cross_edges(g_acc, variable_vertex, cross_neighbors, component, remove_edge_fun)
}
{:value, _} ->
acc
_ ->
acc
end
end)
## drop 1-vertex sccs
updated_components =
MapSet.size(variable_vertices) > 1 && MapSet.put(component_acc, variable_vertices) || component_acc
{updated_components, updated_graph}
end
# defp remove_cross_edges(graph, variable_vertex, neighbors, component, remove_edge_fun) do
# case Iterable.next(neighbors) do
# :done -> graph
# {:ok, neighbor, rest} ->
# updated_graph =
# (neighbor in component) && graph ||
# remove_edge_fun.(graph, variable_vertex, BitGraph.V.get_vertex(graph, neighbor))
# remove_cross_edges(updated_graph, variable_vertex, rest, component, remove_edge_fun)
# end
# end
defp remove_cross_edges(graph, variable_vertex, neighbors, component, remove_edge_fun) do
N.iterate(neighbors, graph, fn neighbor, acc ->
if neighbor in component do
{:cont, acc}
else
{:cont, remove_edge_fun.(acc, variable_vertex, BitGraph.V.get_vertex(acc, neighbor))}
end
end)
end
def default_remove_edge_fun(vars) do
fn graph, {:variable, var_index} = var_vertex, {:value, value} = value_vertex ->
var = ValueGraph.get_variable(vars, var_index)
if Interface.fixed?(var) do
Interface.min(var) == value && graph || throw(:fail)
else
ValueGraph.delete_edge(graph, var_vertex, value_vertex, vars)
end
end
end
end