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