defmodule CPSolver.Propagator.Reified do
use CPSolver.Propagator
alias CPSolver.BooleanVariable, as: BoolVar
alias CPSolver.Propagator
@moduledoc """
The propagator for reification constraints.
Full reification:
1. If b is fixed to 1, the propagator for the reification reduces to a propagator for C.
2. If b is fixed to 0, the propagator for the reification reduces to a propagator for opposite(C).
3. If a propagator for C would realize that the C would be entailed, the propagator for the reification fixes b to 1 and ceases to exist.
4. If a propagator for C would realize that the C would fail, the propagator for the reification fixes x b to 0 and ceases to exist.
Half-reification:
Rules 2 and 3 of full reification.
Inverse implication:
Rules 1 and 4 of full reification.
"""
def new(propagators, b_var, mode) when mode in [:full, :half, :inverse_half] do
new([propagators, b_var, mode])
end
@impl true
def variables([propagators, b_var, _mode]) do
Enum.reduce(
propagators,
[set_propagate_on(b_var, :fixed)],
fn p, acc -> acc ++ Propagator.variables(p) end
)
|> Enum.uniq()
end
@impl true
def filter(args, nil, changes) do
filter(args, initial_state(args), changes)
end
def filter(
[_propagators, b_var, mode] = _args,
%{active_propagators: active_propagators} = _state,
changes
) do
filter_impl(mode, b_var, active_propagators, changes)
end
@impl true
def bind(
%{args: [propagators, b_var, mode] = _args, state: state} = propagator,
source,
var_field
) do
bound_propagators = Enum.map(propagators, fn p -> Propagator.bind(p, source, var_field) end)
Map.put(propagator, :args, [
bound_propagators,
Propagator.bind_to_variable(b_var, source, var_field),
mode
])
|> Map.put(:state, %{active_propagators: bound_propagators, b_idx: state[:b_idx]})
end
defp actions() do
%{
:full => [
&propagate/2,
&propagate_negative/2,
&terminate_false/1,
&terminate_true/1
],
:half => [nil, &propagate_negative/2, nil, &terminate_true/1],
:inverse_half => [&propagate/2, nil, &terminate_false/1, nil]
}
end
## Callbacks for reified
defp propagate(propagators, incoming_changes) do
res =
Enum.reduce_while(propagators, [], fn p, active_propagators_acc ->
case Propagator.filter(p, changes: incoming_changes) do
:fail ->
throw(:fail)
%{active?: active?} ->
{:cont, (active? && [p | active_propagators_acc]) || active_propagators_acc}
:stable ->
{:cont, [p | active_propagators_acc]}
end
end)
cond do
res == :fail -> :fail
Enum.empty?(res) -> :passive
true -> {:state, %{active_propagators: res}}
end
end
defp propagate_negative(propagators, changes) do
propagators
|> opposite_propagators()
|> propagate(changes)
end
defp terminate_true(b_var) do
terminate_propagator(b_var, true)
end
defp terminate_false(b_var) do
terminate_propagator(b_var, false)
end
defp terminate_propagator(b_var, bool) do
(bool && fix(b_var, 1)) || fix(b_var, 0)
:passive
end
defp filter_impl(mode, b_var, propagators, changes) do
[propagate_action, propagate_negative_action, fix_to_false_action, fix_to_true_action] =
Map.get(actions(), mode)
cond do
BoolVar.true?(b_var) ->
propagate_action && propagate_action.(propagators, changes) && active_state(propagators)
BoolVar.false?(b_var) ->
propagate_negative_action && propagate_negative_action.(propagators, changes) &&
active_state(propagators)
true ->
## Control variable is not fixed
case check_propagators(propagators, changes) do
:fail ->
fix_to_false_action && fix_to_false_action.(b_var) && active_state(propagators)
:entailed ->
fix_to_true_action && fix_to_true_action.(b_var) && active_state(propagators)
active_propagators ->
active_state(active_propagators)
end
end
end
defp initial_state([propagators, _b_var, _mode] = args) do
%{active_propagators: propagators, b_idx: length(variables(args)) - 1}
end
defp check_propagators(propagators, _incoming_changes) do
propagators
|> Enum.reduce_while(
[],
fn p, active_propagators_acc ->
cond do
Propagator.failed?(p) ->
{:halt, :fail}
Propagator.entailed?(p) ->
{:cont, active_propagators_acc}
true ->
{:cont, [p | active_propagators_acc]}
end
end
)
|> case do
:fail ->
:fail
active_propagators ->
(Enum.empty?(active_propagators) && :entailed) || active_propagators
end
end
defp opposite_propagators(propagators) do
Enum.map(propagators, fn p -> opposite(p) end)
end
## Opposite propagators
alias CPSolver.Propagator.{Equal, NotEqual, Less, LessOrEqual, Absolute, AbsoluteNotEqual}
defp opposite(%{mod: Equal} = p) do
%{p | mod: NotEqual}
end
defp opposite(%{mod: NotEqual} = p) do
%{p | mod: Equal}
end
defp opposite(%{mod: LessOrEqual, args: [x, y, offset]} = p) do
%{p | mod: Less, args: [y, x, -offset]}
end
defp opposite(%{mod: Absolute} = p) do
%{p | mod: AbsoluteNotEqual}
end
defp active_state(propagators) do
{:state, %{active?: true, active_propagators: propagators}}
end
end