lib/solver/search/strategy/value/partition.ex

defmodule CPSolver.Search.Partition do
  alias CPSolver.DefaultDomain, as: Domain

  alias CPSolver.Variable.Interface

  alias CPSolver.Search.ValueSelector.{Min, Max, Random, Split}

  require Logger

  def initialize(partition, _space_data) do
    ## TODO:
    # strategy(partition).initialize(space_data)
    partition
  end

  def partition(variable, value_choice) do
    {:ok, partition_impl(variable, value_choice)}
  end

  defp partition_impl(variable, value_choice) when is_function(value_choice) do
    value_choice.(variable)
    |> partition_by_fix(variable)
  end

  defp partition_impl(variable, value_choice) when is_atom(value_choice) do
    domain = Interface.domain(variable)
    impl = strategy(value_choice)


    selected_value = impl.select_value(variable)

    impl.partition(selected_value)
    |> Enum.map(fn partition_fun ->
      d_copy = Domain.copy(domain)
      domain_changes = partition_fun.(d_copy) |> normalize_domain_changes()
      {
        d_copy,
        %{variable.id => domain_changes}
      }
    end)
  end

  defp normalize_domain_changes({changes, _domain}), do: changes
  defp normalize_domain_changes(changes) when is_atom(changes), do: changes

  defp strategy(:indomain_min) do
    Min
  end

  defp strategy(:indomain_max) do
    Max
  end

  defp strategy(:indomain_random) do
    Random
  end

  defp strategy(:indomain_split) do
    Split
  end

  defp strategy(impl) when is_atom(impl) do
    if Code.ensure_loaded(impl) == {:module, impl} && function_exported?(impl, :select, 2) do
      impl
    else
      throw({:unknown_strategy, impl})
    end
  end

  ## Default partitioning
  defp partition_by_fix(value, variable) do
    domain = Interface.domain(variable)

    try do
      {remove_changes, _domain} = Domain.remove(domain, value)

       [
         {
           Domain.new(value),
           %{variable.id => :fixed}
           # Equal.new(variable, value)
         },
         {
           domain,
           %{variable.id => remove_changes}
           # NotEqual.new(variable, value)
         }
       ]
      catch
      :fail ->
        Logger.error(
          "Failure on partitioning with value #{inspect(value)}, domain: #{inspect(CPSolver.BitVectorDomain.raw(domain))}"
        )

        throw(:fail)
    end
  end
end