lib/solver/search/strategy/variable/local/most_completed.ex

defmodule CPSolver.Search.VariableSelector.MostCompleted do
  use CPSolver.Search.VariableSelector
  alias CPSolver.Propagator.ConstraintGraph

  @impl true
  def select(_variables, space_data, _opts) do
    most_completed_propagators_selection(space_data[:constraint_graph])
  end

  ## Choose variables connected to "most completed" propagators.
  ## 1) Choose propagators with smallest number of still unfixed variables
  ## (this corresponds to propagators with smallest degree in constraint graph).
  ## 2) Choose variables most constrained by the propagators above.
  def most_completed_propagators_selection(constraint_graph) do
    ## Make p => (unfixed variables) map
    constraint_graph
    |> ConstraintGraph.edges()
    |> Enum.group_by(
      fn edge -> edge.v2 end,
      fn edge -> edge.v1 end
    )
    ## Pick out the propagators with minimal number of unfixed variables.
    ## Build the list of variable ids constrained by those propagators.
    |> Enum.reduce(
      {[], nil},
      fn {_propagator_id, var_ids}, {var_ids_acc, current_min} = acc ->
        var_count = length(var_ids)

        cond do
          is_nil(current_min) || var_count < current_min -> {var_ids, var_count}
          var_count > current_min -> acc
          var_count == current_min -> {var_ids ++ var_ids_acc, var_count}
        end
      end
    )
    |> elem(0)
    ## Choose variables with the largest counts of constraints (i.e., attached propagators)
    |> Enum.frequencies()
    |> Enum.reduce({[], nil}, fn {var_id, var_count}, {vars_acc, current_max} = acc ->
      graph_var = ConstraintGraph.get_variable(constraint_graph, var_id)

      cond do
        is_nil(current_max) || var_count > current_max -> {[graph_var], var_count}
        var_count < current_max -> acc
        var_count == current_max -> {[graph_var | vars_acc], var_count}
      end
    end)
    |> elem(0)
  end
end