From 9852439f23c1e677582b78bb48d88ddfc714ade2 Mon Sep 17 00:00:00 2001 From: Boris Okner Date: Wed, 25 Sep 2024 21:43:28 -0400 Subject: [PATCH 1/4] Refactoring (in progress) --- .../propagators/all_different_fwc.ex | 189 ++++-------------- 1 file changed, 39 insertions(+), 150 deletions(-) diff --git a/lib/solver/constraints/propagators/all_different_fwc.ex b/lib/solver/constraints/propagators/all_different_fwc.ex index 8ceaed9..47d9e43 100644 --- a/lib/solver/constraints/propagators/all_different_fwc.ex +++ b/lib/solver/constraints/propagators/all_different_fwc.ex @@ -5,51 +5,6 @@ defmodule CPSolver.Propagator.AllDifferent.FWC do The forward-checking propagator for AllDifferent constraint. """ - @impl true - def reset(args, nil, _opts) do - {initial_unfixed_vars, initial_fixed_values} = initial_reduction(args) - %{unfixed_vars: initial_unfixed_vars, fixed_values: initial_fixed_values} - end - - def reset(args, %{fixed_values: fixed_values, unfixed_vars: unfixed_vars} = _state, _opts) do - {unfixed_vars, delta, total_fixed} = - Enum.reduce( - unfixed_vars, - {unfixed_vars, MapSet.new(), fixed_values}, - fn idx, - {unfixed_acc, delta_acc, total_fixed_acc} = - acc -> - case get_value(args, idx) do - nil -> - acc - - value -> - {MapSet.delete(unfixed_acc, idx), add_fixed_value(delta_acc, value), - add_fixed_value(total_fixed_acc, value)} - end - end - ) - - {final_unfixed_vars, final_fixed_values} = fwc(args, unfixed_vars, delta, total_fixed) - %{unfixed_vars: final_unfixed_vars, fixed_values: final_fixed_values} - end - - defp initial_reduction(args) do - Arrays.reduce( - args, - {0, {MapSet.new(), MapSet.new()}}, - fn var, {idx_acc, {unfixed_map_acc, fixed_set_acc}} -> - {idx_acc + 1, - (fixed?(var) && {unfixed_map_acc, add_fixed_value(fixed_set_acc, min(var))}) || - {MapSet.put(unfixed_map_acc, idx_acc), fixed_set_acc}} - end - ) - |> elem(1) - |> then(fn {unfixed_vars, fixed_values} -> - fwc(args, unfixed_vars, fixed_values, fixed_values) - end) - end - @impl true def arguments(args) do Arrays.new(args, implementation: Aja.Vector) @@ -62,124 +17,58 @@ defmodule CPSolver.Propagator.AllDifferent.FWC do @impl true def filter(all_vars, state, changes) do - {unfixed_vars, fixed_values} = - if state do - {state.unfixed_vars, state.fixed_values} - else - initial_reduction(all_vars) - end - - {updated_unfixed_vars, updated_fixed_values} = - filter_impl(all_vars, unfixed_vars, fixed_values, changes) - - {:state, %{unfixed_vars: updated_unfixed_vars, fixed_values: updated_fixed_values}} + unfixed_set = state && state[:unfixed] || MapSet.new(0..Arrays.size(all_vars) - 1) + case fwc(all_vars, unfixed_set, fixed_values(changes, all_vars)) do + nil -> :passive + unfixed_updated_set -> {:state, %{unfixed: unfixed_updated_set}} + end end - defp filter_impl(all_vars, unfixed_vars, fixed_values, changes) when is_map(changes) do - {new_unfixed_vars, new_fixed_values, all_fixed_values} = - prepare_changes(all_vars, unfixed_vars, fixed_values, changes) + defp fixed_values(changes, arg_vars) do + Enum.reduce(changes, MapSet.new(), fn {var_idx, :fixed}, acc -> + MapSet.put(acc, min(Propagator.arg_at(arg_vars, var_idx))) - fwc(all_vars, new_unfixed_vars, new_fixed_values, all_fixed_values) + end) end - defp prepare_changes(all_vars, unfixed_vars, previously_fixed_values, changes) do - Enum.reduce( - changes, - {unfixed_vars, MapSet.new(), previously_fixed_values}, - fn {idx, :fixed}, {unfixed_vars_acc, fixed_values_acc, all_fixed_values_acc} = acc -> - if MapSet.member?(unfixed_vars_acc, idx) do - updated_vars = MapSet.delete(unfixed_vars_acc, idx) - fixed_value = get_value(all_vars, idx) - {updated_vars, add_fixed_value(fixed_values_acc, fixed_value), - add_fixed_value(all_fixed_values_acc, fixed_value)} + ## unfixed_set - set of indices for yet unfixed variables + ## fixed_values - the set of fixed values we will use to reduce unfixed set. + defp fwc(vars, unfixed_set, fixed_values) do + {reduced_unfixed, step_fixed, _total_fixed} = + Enum.reduce(unfixed_set, {MapSet.new(), MapSet.new(), fixed_values}, + fn unfixed_idx, {reduced_unfixed_acc, step_fixed_acc, total_fixed_acc} -> + var = Propagator.arg_at(vars, unfixed_idx) + if remove_all(var, total_fixed_acc) == :fixed do + ## New fixed variable, add to fixed values + new_fixed_value = min(var) + {reduced_unfixed_acc, + MapSet.put(total_fixed_acc, new_fixed_value), + MapSet.put(total_fixed_acc, new_fixed_value) + } else - acc - end - end - ) - end - - defp fwc(all_vars, unfixed_vars, current_delta, accumulated_fixed_values) do - {updated_unfixed_vars, _fixed_values, new_delta} = - Enum.reduce( - unfixed_vars, - {unfixed_vars, current_delta, MapSet.new()}, - fn idx, {unfixed_vars_acc, fixed_values_acc, new_delta_acc} -> - case remove_all(get_variable(all_vars, idx), fixed_values_acc) do - ## No new fixed variables - false -> - {unfixed_vars_acc, fixed_values_acc, new_delta_acc} - - new_fixed_value -> - {MapSet.delete(unfixed_vars_acc, idx), - MapSet.put(fixed_values_acc, new_fixed_value), - MapSet.put(new_delta_acc, new_fixed_value)} - end + ## Still unfixed, add to unfixed set + {MapSet.put(reduced_unfixed_acc, unfixed_idx), + step_fixed_acc, + total_fixed_acc + } end - ) - - updated_accumulated_fixed_values = MapSet.union(accumulated_fixed_values, new_delta) - - if MapSet.size(new_delta) == 0 do - {updated_unfixed_vars, updated_accumulated_fixed_values} - else - fwc(all_vars, updated_unfixed_vars, new_delta, updated_accumulated_fixed_values) - end - - ## - end - - ## Remove values from the domain of variable - ## Note: if the variable gets fixed at some point, - ## we can stop by checking if the fixed value is already present in the set of values. - ## If that's the case, we'll fail (duplicate fixed value!), - ## otherwise we exit the loop, as there is no point to continue. - defp remove_all(nil, _values) do - false - end - - defp remove_all(variable, values) do - Enum.reduce_while( - values, - false, - fn value, _acc -> - case remove(variable, value) do - :fixed -> - fixed_value = min(variable) - (MapSet.member?(values, fixed_value) && throw(:fail)) || {:halt, fixed_value} + end) - _not_fixed -> - {:cont, false} - end + cond do + MapSet.size(reduced_unfixed) <= 1 -> nil + MapSet.size(step_fixed) == 0 -> reduced_unfixed + true -> + fwc(vars, reduced_unfixed, step_fixed) end - ) - end - - defp add_fixed_value(fixed_values, nil) do - fixed_values - end - - defp add_fixed_value(fixed_values, value) do - (MapSet.member?(fixed_values, value) && throw(:fail)) || - MapSet.put(fixed_values, value) end - defp get_value(_variables, nil) do - nil + defp remove_all(var, values) do + Enum.reduce_while(values, nil, fn val, _acc -> + remove(var, val) == :fixed && {:halt, :fixed} + || {:cont, nil} + end) end - defp get_value(variables, idx) do - case get_variable(variables, idx) do - nil -> - nil - var -> - (fixed?(var) && min(var)) || nil - end - end - - defp get_variable(variables, idx) do - (idx && Propagator.arg_at(variables, idx)) || nil - end end From 599e249b544bb95b10e059283896e8dd02775515 Mon Sep 17 00:00:00 2001 From: Boris Okner Date: Thu, 26 Sep 2024 20:55:47 -0400 Subject: [PATCH 2/4] In progress --- .../propagators/all_different_fwc.ex | 31 ++++++++++++++----- test/constraints/all_different_fwc_test.exs | 2 +- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lib/solver/constraints/propagators/all_different_fwc.ex b/lib/solver/constraints/propagators/all_different_fwc.ex index 47d9e43..a8cb54c 100644 --- a/lib/solver/constraints/propagators/all_different_fwc.ex +++ b/lib/solver/constraints/propagators/all_different_fwc.ex @@ -17,17 +17,34 @@ defmodule CPSolver.Propagator.AllDifferent.FWC do @impl true def filter(all_vars, state, changes) do - unfixed_set = state && state[:unfixed] || MapSet.new(0..Arrays.size(all_vars) - 1) - case fwc(all_vars, unfixed_set, fixed_values(changes, all_vars)) do + {unfixed_set, fixed_values} = + if state do + fixed_values = fixed_values(changes, all_vars) + {MapSet.difference(state[:unfixed], MapSet.new(Map.keys(changes))), fixed_values} + else + initial_split(all_vars, changes) + end + #initial_reduction(all_vars, fixed_values) + case fwc(all_vars, unfixed_set, fixed_values) do nil -> :passive unfixed_updated_set -> {:state, %{unfixed: unfixed_updated_set}} end end + defp initial_split(all_vars, changes) do + {_, unfixed_set, fixed_values} = Enum.reduce(all_vars, {0, MapSet.new(), MapSet.new(Map.keys(changes))}, + fn var, {idx, unfixed_set_acc, fixed_set_acc} -> + (remove_all(var, fixed_set_acc) == :fixed || fixed?(var)) && {idx + 1, unfixed_set_acc, MapSet.put(fixed_set_acc, min(var))} + || {idx + 1, MapSet.put(unfixed_set_acc, idx), fixed_set_acc} + + end) + {unfixed_set, fixed_values} + end + defp fixed_values(changes, arg_vars) do + Enum.reduce(changes, MapSet.new(), fn {var_idx, :fixed}, acc -> MapSet.put(acc, min(Propagator.arg_at(arg_vars, var_idx))) - end) end @@ -39,7 +56,7 @@ defmodule CPSolver.Propagator.AllDifferent.FWC do Enum.reduce(unfixed_set, {MapSet.new(), MapSet.new(), fixed_values}, fn unfixed_idx, {reduced_unfixed_acc, step_fixed_acc, total_fixed_acc} -> var = Propagator.arg_at(vars, unfixed_idx) - if remove_all(var, total_fixed_acc) == :fixed do + if remove_all(var, total_fixed_acc) == :fixed || fixed?(var) do ## New fixed variable, add to fixed values new_fixed_value = min(var) {reduced_unfixed_acc, @@ -56,7 +73,7 @@ defmodule CPSolver.Propagator.AllDifferent.FWC do end) cond do - MapSet.size(reduced_unfixed) <= 1 -> nil + MapSet.size(reduced_unfixed) == 0 -> nil MapSet.size(step_fixed) == 0 -> reduced_unfixed true -> fwc(vars, reduced_unfixed, step_fixed) @@ -64,9 +81,9 @@ defmodule CPSolver.Propagator.AllDifferent.FWC do end defp remove_all(var, values) do - Enum.reduce_while(values, nil, fn val, _acc -> + Enum.reduce_while(values, nil, fn val, acc -> remove(var, val) == :fixed && {:halt, :fixed} - || {:cont, nil} + || {:cont, acc} end) end diff --git a/test/constraints/all_different_fwc_test.exs b/test/constraints/all_different_fwc_test.exs index eceace4..89df8ff 100644 --- a/test/constraints/all_different_fwc_test.exs +++ b/test/constraints/all_different_fwc_test.exs @@ -11,7 +11,7 @@ defmodule CPSolverTest.Constraint.AllDifferent.FWC do test "all fixed" do variables = Enum.map(1..5, fn i -> IntVariable.new(i) end) model = Model.new(variables, [Constraint.new(AllDifferentFWC, variables)]) - {:ok, result} = CPSolver.solve_sync(model, timeout: 100) + {:ok, result} = CPSolver.solve_sync(model) assert hd(result.solutions) == [1, 2, 3, 4, 5] assert result.statistics.solution_count == 1 From f1edfc777e8c9fb7d4955914485cc626281129fc Mon Sep 17 00:00:00 2001 From: Boris Okner Date: Thu, 26 Sep 2024 22:50:36 -0400 Subject: [PATCH 3/4] Rework (wip) --- .../propagators/all_different_fwc.ex | 80 ++++++++----------- 1 file changed, 33 insertions(+), 47 deletions(-) diff --git a/lib/solver/constraints/propagators/all_different_fwc.ex b/lib/solver/constraints/propagators/all_different_fwc.ex index a8cb54c..9873bd1 100644 --- a/lib/solver/constraints/propagators/all_different_fwc.ex +++ b/lib/solver/constraints/propagators/all_different_fwc.ex @@ -17,67 +17,49 @@ defmodule CPSolver.Propagator.AllDifferent.FWC do @impl true def filter(all_vars, state, changes) do - {unfixed_set, fixed_values} = - if state do - fixed_values = fixed_values(changes, all_vars) - {MapSet.difference(state[:unfixed], MapSet.new(Map.keys(changes))), fixed_values} - else - initial_split(all_vars, changes) + new_fixed = Map.keys(changes) |> MapSet.new() + unresolved = (state && state[:unresolved] || MapSet.new(0..Arrays.size(all_vars) - 1)) + |> MapSet.difference(new_fixed) + + new_fixed_values = fixed_values(all_vars, new_fixed) + + case fwc(all_vars, unresolved, new_fixed_values) do + false -> :passive + unfixed_updated_set -> {:state, %{unresolved: unfixed_updated_set}} end - #initial_reduction(all_vars, fixed_values) - case fwc(all_vars, unfixed_set, fixed_values) do - nil -> :passive - unfixed_updated_set -> {:state, %{unfixed: unfixed_updated_set}} - end end - defp initial_split(all_vars, changes) do - {_, unfixed_set, fixed_values} = Enum.reduce(all_vars, {0, MapSet.new(), MapSet.new(Map.keys(changes))}, - fn var, {idx, unfixed_set_acc, fixed_set_acc} -> - (remove_all(var, fixed_set_acc) == :fixed || fixed?(var)) && {idx + 1, unfixed_set_acc, MapSet.put(fixed_set_acc, min(var))} - || {idx + 1, MapSet.put(unfixed_set_acc, idx), fixed_set_acc} - + defp fixed_values(vars, fixed) do + Enum.reduce(fixed, MapSet.new(), fn idx, values_acc -> + val = Propagator.arg_at(vars, idx) |> min() + val in values_acc && fail() || MapSet.put(values_acc, val) end) - {unfixed_set, fixed_values} end - defp fixed_values(changes, arg_vars) do + defp fwc(vars, unfixed_set, fixed_values) do + {updated_unfixed, _fixed_vals} = remove_values(vars, unfixed_set, fixed_values) + MapSet.size(updated_unfixed) > 0 && updated_unfixed - Enum.reduce(changes, MapSet.new(), fn {var_idx, :fixed}, acc -> - MapSet.put(acc, min(Propagator.arg_at(arg_vars, var_idx))) - end) end - ## unfixed_set - set of indices for yet unfixed variables ## fixed_values - the set of fixed values we will use to reduce unfixed set. - defp fwc(vars, unfixed_set, fixed_values) do - {reduced_unfixed, step_fixed, _total_fixed} = - Enum.reduce(unfixed_set, {MapSet.new(), MapSet.new(), fixed_values}, - fn unfixed_idx, {reduced_unfixed_acc, step_fixed_acc, total_fixed_acc} -> - var = Propagator.arg_at(vars, unfixed_idx) - if remove_all(var, total_fixed_acc) == :fixed || fixed?(var) do - ## New fixed variable, add to fixed values + defp remove_values(vars, unfixed_set, fixed_values) do + for idx <- unfixed_set, reduce: {MapSet.new(), fixed_values} do + {still_unfixed_acc, fixed_vals_acc} -> + var = Propagator.arg_at(vars, idx) + if remove_all(var, fixed_vals_acc) == :fixed || fixed?(var) do new_fixed_value = min(var) - {reduced_unfixed_acc, - MapSet.put(total_fixed_acc, new_fixed_value), - MapSet.put(total_fixed_acc, new_fixed_value) - } + new_fixed_value in fixed_values && fail() + + fixed_vals_acc = MapSet.put(fixed_vals_acc, new_fixed_value) + {unfixed_here, fixed_here} = remove_values(vars, still_unfixed_acc, MapSet.new([new_fixed_value])) + {unfixed_here, MapSet.union(fixed_here, fixed_vals_acc)} else - ## Still unfixed, add to unfixed set - {MapSet.put(reduced_unfixed_acc, unfixed_idx), - step_fixed_acc, - total_fixed_acc - } + ## Variable is still unfixed, keep it + {MapSet.put(still_unfixed_acc, idx), fixed_vals_acc} end - end) - - cond do - MapSet.size(reduced_unfixed) == 0 -> nil - MapSet.size(step_fixed) == 0 -> reduced_unfixed - true -> - fwc(vars, reduced_unfixed, step_fixed) - end + end end defp remove_all(var, values) do @@ -87,5 +69,9 @@ defmodule CPSolver.Propagator.AllDifferent.FWC do end) end + defp fail() do + throw(:fail) + end + end From 22c917683e65d8dc6294c7f206c8eb39d1d6131d Mon Sep 17 00:00:00 2001 From: Boris Okner Date: Sat, 28 Sep 2024 13:19:13 -0400 Subject: [PATCH 4/4] Stable --- lib/solver/constraints/circuit.ex | 2 +- .../propagators/all_different_fwc.ex | 79 +++++++++++++------ 2 files changed, 54 insertions(+), 27 deletions(-) diff --git a/lib/solver/constraints/circuit.ex b/lib/solver/constraints/circuit.ex index ad6c31f..dd1e07c 100644 --- a/lib/solver/constraints/circuit.ex +++ b/lib/solver/constraints/circuit.ex @@ -6,6 +6,6 @@ defmodule CPSolver.Constraint.Circuit do @impl true def propagators(x) do - [CircuitPropagator.new(x), AllDifferentPropagator.new(x)] + [CircuitPropagator.new(x)] end end diff --git a/lib/solver/constraints/propagators/all_different_fwc.ex b/lib/solver/constraints/propagators/all_different_fwc.ex index 9873bd1..92c0384 100644 --- a/lib/solver/constraints/propagators/all_different_fwc.ex +++ b/lib/solver/constraints/propagators/all_different_fwc.ex @@ -17,29 +17,44 @@ defmodule CPSolver.Propagator.AllDifferent.FWC do @impl true def filter(all_vars, state, changes) do - new_fixed = Map.keys(changes) |> MapSet.new() - unresolved = (state && state[:unresolved] || MapSet.new(0..Arrays.size(all_vars) - 1)) - |> MapSet.difference(new_fixed) + new_fixed = Map.keys(changes) |> MapSet.new() - new_fixed_values = fixed_values(all_vars, new_fixed) + {unresolved, fixed} = + (state && + {state[:unresolved] |> MapSet.difference(new_fixed), fixed_values(all_vars, new_fixed)}) || + initial_split(all_vars) - case fwc(all_vars, unresolved, new_fixed_values) do - false -> :passive - unfixed_updated_set -> {:state, %{unresolved: unfixed_updated_set}} - end + case fwc(all_vars, unresolved, fixed) do + false -> :passive + unfixed_updated_set -> {:state, %{unresolved: unfixed_updated_set}} + end end defp fixed_values(vars, fixed) do Enum.reduce(fixed, MapSet.new(), fn idx, values_acc -> val = Propagator.arg_at(vars, idx) |> min() - val in values_acc && fail() || MapSet.put(values_acc, val) + (val in values_acc && fail()) || MapSet.put(values_acc, val) + end) + end + + defp initial_split(vars) do + Enum.reduce(0..(Arrays.size(vars) - 1), {MapSet.new(), MapSet.new()}, fn idx, + {unfixed_acc, + fixed_vals_acc} -> + var = Propagator.arg_at(vars, idx) + + if fixed?(var) do + val = min(var) + (val in fixed_vals_acc && fail()) || {unfixed_acc, MapSet.put(fixed_vals_acc, val)} + else + {MapSet.put(unfixed_acc, idx), fixed_vals_acc} + end end) end defp fwc(vars, unfixed_set, fixed_values) do {updated_unfixed, _fixed_vals} = remove_values(vars, unfixed_set, fixed_values) - MapSet.size(updated_unfixed) > 0 && updated_unfixed - + MapSet.size(updated_unfixed) > 1 && updated_unfixed end ## unfixed_set - set of indices for yet unfixed variables @@ -48,30 +63,42 @@ defmodule CPSolver.Propagator.AllDifferent.FWC do for idx <- unfixed_set, reduce: {MapSet.new(), fixed_values} do {still_unfixed_acc, fixed_vals_acc} -> var = Propagator.arg_at(vars, idx) - if remove_all(var, fixed_vals_acc) == :fixed || fixed?(var) do - new_fixed_value = min(var) - new_fixed_value in fixed_values && fail() - - fixed_vals_acc = MapSet.put(fixed_vals_acc, new_fixed_value) - {unfixed_here, fixed_here} = remove_values(vars, still_unfixed_acc, MapSet.new([new_fixed_value])) - {unfixed_here, MapSet.union(fixed_here, fixed_vals_acc)} - else - ## Variable is still unfixed, keep it - {MapSet.put(still_unfixed_acc, idx), fixed_vals_acc} + + case remove_all(var, fixed_vals_acc) do + false -> + ## Variable is still unfixed, keep it + {MapSet.put(still_unfixed_acc, idx), fixed_vals_acc} + + new_fixed_value -> + fixed_vals_acc = MapSet.put(fixed_vals_acc, new_fixed_value) + + {unfixed_here, fixed_here} = + remove_values(vars, still_unfixed_acc, MapSet.new([new_fixed_value])) + + {unfixed_here, MapSet.union(fixed_here, fixed_vals_acc)} end end end defp remove_all(var, values) do - Enum.reduce_while(values, nil, fn val, acc -> - remove(var, val) == :fixed && {:halt, :fixed} - || {:cont, acc} + Enum.reduce_while(values, false, fn val, acc -> + if remove(var, val) == :fixed do + {:halt, :fixed} + else + {:cont, acc} + end end) + |> case do + false -> + fixed?(var) && min(var) + + :fixed -> + min(var) + end + |> then(fn new_min -> new_min && ((new_min in values && fail()) || new_min) end) end defp fail() do throw(:fail) end - - end