defmodule CPSolver.Propagator.AllDifferent.Utils do alias CPSolver.ValueGraph alias CPSolver.Variable.Interface ## 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} = v -> foreign_neighbors = BitGraph.E.out_neighbors(g_acc, vertex_index) { MapSet.put(vertices_acc, variable_id), Enum.reduce(foreign_neighbors, g_acc, fn neighbor, g_acc2 when is_integer(neighbor) -> (neighbor in component && g_acc2) || remove_edge_fun.(g_acc2, v, BitGraph.V.get_vertex(g_acc2, neighbor)) end) } {: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 def default_remove_edge_fun(vars) do fn graph, {:variable, var_index} = var_vertex, value_vertex -> var = ValueGraph.get_variable(vars, var_index) if Interface.fixed?(var) do graph else ValueGraph.delete_edge(graph, var_vertex, get_value_vertex(value_vertex), vars) end end end ## Helpers defp get_value_vertex({:value, _vertex} = v) do v end defp get_value_vertex(vertex) when is_integer(vertex) do {:value, vertex} end end