defmodule CPSolver.Propagator.Channel do
use CPSolver.Propagator
def new(x, bools) do
b_vars_num = length(bools)
removeBelow(x, 1)
removeAbove(x, b_vars_num)
Enum.each(bools, fn b_var ->
removeAbove(b_var, 1)
removeBelow(b_var, 0)
end)
new([x | bools])
end
@impl true
def variables([x | b_vars]) do
[
set_propagate_on(x, :domain_change)
| Enum.map(b_vars, fn b_var -> set_propagate_on(b_var, :fixed) end)
]
end
@impl true
def arguments(args) do
Vector.new(args)
end
@impl true
def filter(vars, state, changes) do
state = state || initial_state(vars)
reduce(vars, state, changes)
|> finalize()
end
defp initial_state(vars) do
bool_var_indices = MapSet.new(1..(Vector.size(vars) - 1))
%{
unfixed_vars: bool_var_indices
}
end
defp reduce(vars, %{unfixed_vars: unfixed_b_var_indices} = _state, changes) do
x_var = vars[0]
## Apply changes
stage1_results =
Enum.reduce_while(changes, unfixed_b_var_indices, fn
{0, :fixed}, acc ->
## x is fixed, we're done
{:halt, {:entailed, min(x_var), acc}}
{0, _other_x_change}, acc ->
{:cont,
Enum.reduce(domain_values(x_var), acc, fn idx, acc2 ->
if idx in acc2 do
acc2
else
fix(vars[idx], 0)
MapSet.delete(acc2, idx)
end
end)}
{b_var_idx, :fixed}, acc ->
acc = MapSet.delete(acc, b_var_idx)
## one of booleans is fixed
b_min = min(vars[b_var_idx])
if b_min == 1 do
fix(x_var, b_var_idx)
{:halt, {:entailed, b_var_idx, acc}}
else
case remove(x_var, b_var_idx) do
:fixed ->
{:halt, {:entailed, min(x_var), acc}}
_ ->
{:cont, acc}
end
end
end)
## Stage 2 : iterate through the rest of unfixed indices
case stage1_results do
{:entailed, x_value, unfixed_bool_indices} ->
fix_booleans(x_value, vars, unfixed_bool_indices)
:entailed
unfixed_bool_indices ->
if fixed?(x_var) do
fix_booleans(min(x_var), vars, unfixed_bool_indices)
:entailed
else
reduce_stage2(x_var, vars, unfixed_bool_indices)
end
end
end
defp fix_booleans(fixed_index, vars, unfixed_b_var_indices) do
Enum.each(
unfixed_b_var_indices,
fn b_index ->
fix_to = (b_index == fixed_index && 1) || 0
fix(vars[b_index], fix_to)
end
)
end
defp reduce_stage2(x_var, vars, unfixed_bool_indices) do
Enum.reduce_while(unfixed_bool_indices, unfixed_bool_indices, fn b_var_idx, acc ->
b_var = vars[b_var_idx]
if fixed?(b_var) do
if min(b_var) == 1 do
fix(x_var, b_var_idx)
fix_booleans(b_var_idx, vars, acc)
{:halt, :entailed}
else
case remove(x_var, b_var_idx) do
:fixed ->
{:halt, :entailed}
_ ->
{:cont, acc}
end
end
else
if contains?(x_var, b_var_idx) do
{:cont, acc}
else
fix(b_var, 0)
{:cont, MapSet.delete(acc, b_var_idx)}
end
end
end)
end
defp finalize(:entailed) do
:passive
end
defp finalize(unfixed_b_var_indices) do
if Enum.empty?(unfixed_b_var_indices) do
:passive
else
{:state, %{unfixed_vars: unfixed_b_var_indices}}
end
end
end