defmodule CPSolver.Propagator.Or do
use CPSolver.Propagator
@moduledoc """
The propagator for 'or' constraint.
Takes the list of boolean variables.
Constraints to have at least one variable to be resolved to true.
"""
@impl true
def variables(args) do
Enum.map(args, fn x_el -> set_propagate_on(x_el, :fixed) end)
end
@impl true
def arguments(args) do
Arrays.new(args, implementation: Aja.Vector)
end
@impl true
def filter(all_vars, nil, changes) do
case initial_reduction(all_vars) do
:resolved ->
:passive
unfixed ->
(MapSet.size(unfixed) == 0 && fail()) ||
filter(all_vars, %{unfixed: unfixed}, changes)
end
end
def filter(all_vars, %{unfixed: unfixed} = _state, changes) when map_size(changes) == 0 do
Enum.reduce_while(unfixed, unfixed, fn idx, unfixed_acc ->
var = Arrays.get(all_vars, idx)
if fixed?(var) do
if min(var) == 1 do
{:halt, :resolved}
else
{:cont, MapSet.delete(unfixed_acc, idx)}
end
else
{:cont, unfixed_acc}
end
end)
|> finalize(all_vars)
end
def filter(all_vars, %{unfixed: unfixed} = _state, changes) do
Enum.reduce_while(changes, unfixed, fn {var_index, :fixed}, unfixed_acc ->
if MapSet.member?(unfixed_acc, var_index) do
if min(Arrays.get(all_vars, var_index)) == 1 do
{:halt, :resolved}
else
{:cont, MapSet.delete(unfixed_acc, var_index)}
end
else
{:cont, unfixed_acc}
end
end)
|> finalize(all_vars)
end
defp initial_reduction(all_vars) do
Enum.reduce_while(0..(Arrays.size(all_vars) - 1), MapSet.new(), fn var_idx, candidates_acc ->
var = Arrays.get(all_vars, var_idx)
if fixed?(var) do
case min(var) do
0 -> {:cont, candidates_acc}
1 -> {:halt, :resolved}
_other_value -> throw(:not_boolean)
end
else
{:cont, MapSet.put(candidates_acc, var_idx)}
end
end)
end
defp finalize(filtering_result, all_vars) do
case filtering_result do
:resolved ->
:passive
unfixed ->
case MapSet.size(unfixed) do
0 ->
fail()
1 ->
last_var_idx = MapSet.to_list(unfixed) |> List.first()
fix(Arrays.get(all_vars, last_var_idx), 1)
:passive
_more ->
{:state, %{unfixed: unfixed}}
end
end
end
defp fail() do
throw(:fail)
end
end