defmodule CPSolver.Propagator.Absolute do
use CPSolver.Propagator
def new(x, y) do
new([x, y])
end
@impl true
def variables(args) do
args
|> Propagator.default_variables_impl()
|> Enum.map(fn var -> set_propagate_on(var, :bound_change) end)
end
@impl true
def filter([x, y] = args, state, changes) do
((state && map_size(changes) > 0) || initial_reduction(x, y)) && filter_impl(x, y, changes)
cond do
failed?(args) ->
throw(:fail)
entailed?(args) ->
:passive
true ->
{:state, %{active: true}}
end
end
@impl true
def failed?([x, y], _state \\ nil) do
max_y = max(y)
max(y) < 0 ||
(
{abs_min_x, abs_max_x} =
Enum.min_max_by(domain_values(x), fn val -> abs(val) end)
|> then(fn {min_val, max_val} -> {abs(min_val), abs(max_val)} end)
min_y = max(0, min(y))
abs_min_x > max_y || abs_max_x < min_y
)
end
@impl true
def entailed?([x, y], _state \\ nil) do
## x and y have to be fixed...
## y = |x|
fixed?(x) && fixed?(y) && abs(min(x)) == min(y)
end
def filter_impl(x, y, changes) do
## x and y have 0 and 1 indices in the list of args
x_idx = 0
y_idx = 1
Enum.each(
changes,
fn
{idx, _change} when idx == x_idx ->
abs_min_x = abs(min(x))
abs_max_x = abs(max(x))
abs_x_lb = min(abs_min_x, abs_max_x)
abs_x_ub = max(abs_min_x, abs_max_x)
removeBelow(y, min(min(y), abs_x_lb))
removeAbove(y, max(max(y), abs_x_ub))
{idx, _change} when idx == y_idx ->
y_min = min(y)
y_max = max(y)
cond do
min(x) >= 0 ->
removeBelow(x, y_min)
removeAbove(x, y_max)
max(x) <= 0 ->
:ok
removeBelow(x, -y_max)
removeAbove(x, -y_min)
true ->
removeAbove(x, y_max)
removeBelow(x, -y_max)
end
{_idx, _change} ->
:ignore
end
)
fixed?(x) && fix(y, abs(min(x)))
fixed?(y) && fix_abs(x, min(y))
end
defp initial_reduction(x, y) do
## y is non-negative
removeBelow(y, 0)
filter_impl(x, y, %{0 => :domain_change, 1 => :domain_change})
end
defp fix_abs(x, value) do
Enum.each(domain_values(x), fn val -> abs(val) != value && remove(x, val) end)
end
end
defmodule CPSolver.Propagator.AbsoluteNotEqual do
use CPSolver.Propagator
alias CPSolver.Propagator.Absolute
def new(x, y) do
new([x, y])
end
@impl true
defdelegate variables(args), to: Absolute
@impl true
def filter([x, y], _state, _changes) do
filter_impl(x, y)
end
def filter_impl(x, c) when is_integer(c) do
remove(x, c)
remove(x, -c)
:passive
end
def filter_impl(x, y) do
cond do
fixed?(x) ->
remove(y, abs(min(x)))
:passive
fixed?(y) ->
y_val = min(y)
remove(x, y_val)
remove(x, -y_val)
:passive
true ->
{:state, %{active: true}}
end
end
@impl true
def failed?(args, state) do
Absolute.entailed?(args, state)
end
@impl true
def entailed?(args, state) do
Absolute.failed?(args, state)
end
end