defmodule CPSolver.Propagator.AllDifferent.BC do use CPSolver.Propagator alias CPSolver.Utils import CPSolver.Utils.MutableArray alias CPSolver.Utils.MutableArray alias CPSolver.Utils.MutableOrder @moduledoc """ A fast and simple algorithm for bounds consistency of the alldifferent constraint (L“opez et al., 2003) """ @impl true def arguments(args) do Arrays.new(args, implementation: Aja.Vector) end @impl true def variables(args) do Enum.map(args, fn x_el -> set_propagate_on(x_el, :bound_change) end) end @impl true def filter(vars, state, changes) do updated_state = update_state(vars, state, changes) filter_impl(vars, updated_state, changes) {:state, updated_state} end defp initialize_state(vars) do n = Arrays.size(vars) {_, lbs, ubs} = Enum.reduce(vars, {0, [], []}, fn var, {idx, min_acc, max_acc} -> {idx + 1, [min(var) | min_acc], [max(var) | max_acc]} end) %{n: n, minsorted_order: MutableOrder.new(Enum.reverse(lbs)), maxsorted_order: MutableOrder.new(Enum.reverse(ubs)), tree: make_array(2 * n + 2), diffs: make_array(2 * n + 2), hall: make_array(2 * n + 2), bounds: make_array(2 * n + 2), minrank: make_array(n), maxrank: make_array(n) } end defp update_state(vars, _state, _changes) do initialize_state(vars) end defp prepare(%{n: n, minsorted_order: minsorted, maxsorted_order: maxsorted, tree: tree, diffs: diffs, hall: hall, bounds: bounds, minrank: minrank, maxrank: maxrank } = state) do last_min = MutableOrder.get(minsorted, 0) last_max = MutableOrder.get(maxsorted, 0) + 1 last_bound = last_min - 2 last_min_idx = 0 last_max_idx = 0 last_bound_idx = 0 array_update(bounds, 0, last_bound) res = Enum.reduce( 1..(2 * n), %{ last_min: last_min, last_max: last_max, last_bound: last_bound, last_min_idx: last_min_idx, last_max_idx: last_max_idx, last_bound_idx: last_bound_idx }, fn _idx, %{ last_min: last_min, last_max: last_max, last_bound: last_bound, last_min_idx: last_min_idx, last_max_idx: last_max_idx } = acc -> cond do last_min_idx < n && last_min <= last_max -> ## LB values first acc = if last_min > last_bound do ## Record new bounds value and advance bounds index acc |> Map.put(:last_bound, last_min) |> Map.put(:last_bound_idx, acc.last_bound_idx + 1) |> tap(fn acc_ -> array_update(bounds, acc_.last_bound_idx, acc_.last_bound) end) else acc end ## Update minrank array_update(minrank, array_get(minsorted.sort_index, acc.last_min_idx), acc.last_bound_idx) ## Advance last min idx and record new last min value acc = Map.put(acc, :last_min_idx, last_min_idx + 1) if acc.last_min_idx < n do Map.put(acc, :last_min, MutableOrder.get(minsorted, acc.last_min_idx)) else acc end true -> ## Switch to UB values acc = if last_max > last_bound do ## Record new bounds value and advance bounds index acc |> Map.put(:last_bound, last_max) |> Map.put(:last_bound_idx, acc.last_bound_idx + 1) |> tap(fn acc_ -> array_update(bounds, acc_.last_bound_idx, acc_.last_bound) end) else acc end ## Update maxrank array_update(maxrank, array_get(maxsorted.sort_index, acc.last_max_idx), acc.last_bound_idx) ## Advance last max index and record new max value if last_max_idx + 1 < n do acc = Map.put(acc, :last_max_idx, last_max_idx + 1) Map.put( acc, :last_max, MutableOrder.get(maxsorted, acc.last_max_idx) + 1 ) else acc end end end ) array_update(bounds, res.last_bound_idx + 1, array_get(bounds, res.last_bound_idx) + 2) Map.put(state, :n_bounds, res.last_bound_idx) |> Map.put(:tree, tree) |> Map.put(:diffs, diffs) |> Map.put(:hall, hall) |> Map.put(:bounds, bounds) |> Map.put(:minrank, minrank) |> Map.put(:maxrank, maxrank) end defp filter_impl( vars, state, changes ) do state = prepare(state) filtered? = filter_lower(vars, state) filtered? = filter_upper(vars, state) || filtered? filtered? && filter_impl(vars, state, changes) state end defp filter_lower( args, %{ bounds: bounds, minsorted_order: minsorted_order, maxsorted_order: maxsorted_order, minrank: minrank, maxrank: maxrank, tree: tree, hall: hall, diffs: diffs } = state ) do ## Initialize internal structures for idx <- 1..(state.n_bounds + 1) do array_update(tree, idx, idx - 1) array_update(hall, idx, idx - 1) array_update(diffs, idx, array_get(bounds, idx) - array_get(bounds, idx - 1)) end for {var_idx, _ub} <- MutableOrder.to_sorted(maxsorted_order), reduce: false do filter_acc? -> x = array_get(minrank, var_idx) y = array_get(maxrank, var_idx) z = pathmax(tree, x + 1) j = array_get(tree, z) array_add(diffs, z, -1) z = if array_get(diffs, z) == 0 do array_update(tree, z, z + 1) pathmax(tree, array_get(tree, z)) |> tap(fn z -> array_update(tree, z, j) end) else z end pathset(tree, x + 1, z, z) if array_get(diffs, z) < array_get(bounds, z) - array_get(bounds, y), do: fail() hall_x = array_get(hall, x) if hall_x > x do w = pathmax(hall, hall_x) pathset(hall, x, w, w) new_min = array_get(bounds, w) res = removeBelow(args[var_idx], new_min) filter_acc? || (res != :no_change) |> tap(fn changed? -> changed? && MutableOrder.update(minsorted_order, {var_idx, new_min}) end) # ] else filter_acc? end |> tap(fn _ -> if array_get(diffs, z) == array_get(bounds, z) - array_get(bounds, y) do pathset(hall, array_get(hall, y), j - 1, y) array_update(hall, y, j - 1) end end) end end defp filter_upper( args, %{ bounds: bounds, maxsorted_order: maxsorted_order, minsorted_order: minsorted_order, minrank: minrank, maxrank: maxrank, tree: tree, hall: hall, diffs: diffs } = state ) do ## Initialize internal structures for idx <- 0..state.n_bounds do array_update(tree, idx, idx + 1) array_update(hall, idx, idx + 1) array_update(diffs, idx, array_get(bounds, idx + 1) - array_get(bounds, idx)) end for {var_idx, _lb} <- MutableOrder.to_sorted(minsorted_order, :desc), reduce: false do filter_acc? -> x = array_get(maxrank, var_idx) y = array_get(minrank, var_idx) z = pathmin(tree, x - 1) j = array_get(tree, z) array_add(diffs, z, -1) z = if array_get(diffs, z) == 0 do array_update(tree, z, z - 1) pathmin(tree, array_get(tree, z)) |> tap(fn z -> array_update(tree, z, j) end) else z end pathset(tree, x - 1, z, z) if array_get(diffs, z) < array_get(bounds, y) - array_get(bounds, z), do: fail() hall_x = array_get(hall, x) if hall_x < x do w = pathmin(hall, hall_x) pathset(hall, x, w, w) new_max = array_get(bounds, w) - 1 res = removeAbove(args[var_idx], new_max) filter_acc? || (res != :no_change) |> tap(fn changed? -> changed? && MutableOrder.update(maxsorted_order, {var_idx, new_max}) end) else filter_acc? end |> tap(fn _ -> if array_get(diffs, z) == array_get(bounds, y) - array_get(bounds, z) do pathset(hall, array_get(hall, y), j + 1, y) array_update(hall, y, j + 1) end end) end end defp pathmax(tree, i) do case array_get(tree, i) do n when n > i -> pathmax(tree, n) _le -> i end end defp pathmin(tree, i) do case array_get(tree, i) do n when n < i -> pathmin(tree, n) _ge -> i end end defp pathset(tree, path_start, path_end, to) do next = path_start prev = next if prev == path_end do tree else next = array_get(tree, prev) array_update(tree, prev, to) pathset(tree, next, path_end, to) end end defp fail() do throw(:fail) end defp print_state(state) do Map.put(state, :bounds, to_array(state.bounds)) |> Map.put(:minsorted, to_array(state.minsorted)) |> Map.put(:maxsorted, to_array(state.maxsorted)) |> Map.put(:minrank, to_array(state.minrank)) |> Map.put(:maxrank, to_array(state.maxrank)) |> Map.put(:hall, to_array(state.hall)) |> Map.put(:tree, to_array(state.tree)) |> Map.put(:diffs, to_array(state.diffs)) |> IO.inspect(label: :state) end defp make_array(arity) when is_integer(arity) do MutableArray.new(arity) end def test do alias CPSolver.IntVariable, as: Variable alias CPSolver.Utils vars = Enum.map( [{:x1, 3..4}, {:x2, 2..4}, {:x3, 3..4}, {:x4, 2..5}, {:x5, 3..6}, {:x6, 1..6}], # [{:x1, 1..1}, {:x2, 2..2}, {:x3, 3}], fn {name, d} -> Variable.new(d, name: name) end ) args = arguments(vars) state = update_state(args, nil, %{}) |> prepare() print_state(state) filter(args, state, %{}) |> tap(fn r -> IO.inspect(r, label: :state) end) {state, Enum.map(vars, fn v -> try do {v.name, Utils.domain_values(v)} catch _ -> {:fail, v.name} end end)} end end