Packages
fixpoint
0.9.4
0.22.2
0.22.1
0.21.5
0.21.4
0.21.3
0.21.2
0.21.1
0.21.0
0.20.6
0.20.5
0.20.4
0.20.3
0.20.2
0.20.1
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.18.2
0.18.1
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.9
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.13.5
0.13.4
0.13.2
0.13.1
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.12.2
0.12.1
0.11.8
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.46
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.28
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.1.3
0.1.2
0.1.1
0.1.0
Constraint Programming Solver
Current section
Files
Jump to
Current section
Files
lib/solver/core/propagator/propagator.ex
defmodule CPSolver.Propagator do
@type propagator_event :: :domain_change | :bound_change | :min_change | :max_change | :fixed
@callback reset(args :: list(), state :: map()) :: map() | nil
@callback reset(args :: list(), state :: map(), opts :: Keyword.t()) :: map() | nil
@callback bind(Propagator.t(), source :: any(), variable_field :: atom()) :: Propagator.t()
@callback filter(args :: list(), state :: map(), changes :: map()) ::
{:state, map()} | :stable | :fail | propagator_event()
@callback entailed?(Propagator.t(), state :: map() | nil) :: boolean()
@callback failed?(Propagator.t(), state :: map() | nil) :: boolean()
@callback variables(args :: list()) :: list()
@callback arguments(args :: list()) :: Arrays.t()
alias CPSolver.Variable
alias CPSolver.Variable.Interface
alias CPSolver.Variable.View
alias CPSolver.Propagator.Variable, as: PropagatorVariable
alias CPSolver.DefaultDomain, as: Domain
alias CPSolver.Propagator.ConstraintGraph
alias CPSolver.Utils.TupleArray
alias CPSolver.Utils
require Logger
defmacro __using__(_) do
quote do
alias CPSolver.Propagator
alias CPSolver.Variable.Interface
alias CPSolver.DefaultDomain, as: Domain
import CPSolver.Propagator.Variable
import CPSolver.Utils
@behaviour Propagator
def new(args) do
Propagator.new(__MODULE__, arguments(args))
end
def arguments(args) do
args
end
def reset(args, state, _opts) do
reset(args, state)
end
def reset(_args, state) do
state
end
def bind(%{args: args} = propagator, source, var_field) do
Map.put(propagator, :args, Propagator.bind_to_variables(args, source, var_field))
end
def entailed?(args, propagator_state) do
false
end
def failed?(args, _propagator_state) do
false
end
def variables(args) do
Propagator.default_variables_impl(args)
end
defoverridable arguments: 1,
variables: 1,
reset: 2,
reset: 3,
bind: 3,
failed?: 2,
entailed?: 2
end
end
def propagator_events() do
[:domain_change, :bound_change, :min_change, :max_change, :fixed]
end
def default_variables_impl(args) do
args
|> Enum.reject(fn arg -> is_constant_arg(arg) end)
end
def new(mod, args, opts \\ []) do
id = Keyword.get_lazy(opts, :id, fn -> make_ref() end)
name = Keyword.get(opts, :name, id)
%{
id: id,
name: name,
mod: mod,
args: args,
state: nil,
variable_positions:
mod.variables(Enum.to_list(args))
|> Enum.with_index(0)
|> Map.new(fn {var, pos} ->
{Interface.id(var), pos}
end)
}
end
def variables(%{mod: mod, args: args} = _propagator) do
mod_variables = mod.variables(Enum.to_list(args))
mod_variables
|> Enum.with_index()
|> Enum.map(fn {var, idx} -> Map.put(var, :arg_position, idx) end)
end
def reset(%{mod: mod, args: args} = propagator, opts \\ []) do
Map.put(propagator, :state, mod.reset(args, Map.get(propagator, :state), opts))
end
def bind(%{mod: mod} = propagator, source, var_field \\ :domain) do
mod.bind(propagator, source, var_field)
end
def dry_run(%{args: args} = propagator, opts \\ []) do
staged_propagator = %{propagator | args: copy_args(args)}
{staged_propagator, filter(staged_propagator, opts)}
end
def filter(%{mod: mod, args: args, variable_positions: positions_map} = propagator, opts \\ []) do
PropagatorVariable.reset_variable_ops()
state = propagator[:state]
## Propagation changes
## The propagation may reshedule the filtering and pass the changes that woke
## the propagator.
incoming_changes =
case Keyword.get(opts, :changes) do
nil ->
%{}
var_changes ->
Enum.reduce(var_changes, Map.new(), fn {var_id, domain_change},
positional_changes_acc ->
position = (is_integer(var_id) && var_id) || Map.get(positions_map, var_id)
(position && Map.put(positional_changes_acc, position, domain_change)) ||
positional_changes_acc
end)
end
## We will reset the state if required.
## Reset will be forced when the space starts propagation.
reset? = Keyword.get(opts, :reset?, false)
try do
state = (reset? && mod.reset(args, state, opts)) || state
case mod.filter(args, state, incoming_changes) do
:fail ->
:fail
:stable ->
:stable
result ->
get_filter_changes(result)
end
catch
:error, error ->
{:filter_error, {mod, error}}
|> tap(fn _ -> Logger.error(%{mod: mod, error: error, stacktrace: __STACKTRACE__}) end)
:fail ->
:fail
end
|> tap(fn result ->
case Keyword.get(opts, :debug) do
debug_fun when is_function(debug_fun) ->
debug_fun.(propagator, Keyword.drop(opts, [:debug]), result)
nil ->
nil
end
end)
end
## Check if propagator is entailed (i.e., all variables are fixed)
def entailed?(%{mod: mod, args: args} = propagator) do
mod.entailed?(args, propagator[:state])
end
def failed?(%{mod: mod, args: args} = propagator) do
mod.failed?(args, propagator[:state])
end
## How propagator events map to domain events
def to_domain_events(:domain_change) do
[:domain_change | to_domain_events(:bound_change)]
end
def to_domain_events(:bound_change) do
[:min_change, :max_change, :bound_change, :fixed]
end
def to_domain_events(:min_change) do
[:min_change, :fixed]
end
def to_domain_events(:max_change) do
[:max_change, :fixed]
end
def to_domain_events(_fixed) do
[:fixed]
end
@spec get_filter_changes(term()) ::
%{:changes => map(), :state => map(), active?: boolean()}
defp get_filter_changes(propagator_active?) when is_boolean(propagator_active?) do
%{
changes: PropagatorVariable.get_variable_ops(),
active?: propagator_active?,
state: nil
}
end
defp get_filter_changes({:state, state}) do
get_filter_changes(true)
|> Map.put(:state, state)
|> Map.put(:active?, Map.get(state, :active?, true))
end
defp get_filter_changes(result) do
get_filter_changes(result != :passive)
end
def bind_to_variables(args, variable_source, var_field) do
arg_map(args, fn arg ->
bind_to_variable(arg, variable_source, var_field)
end)
end
def bind_to_variable(%Variable{id: id} = propagator_var, variable_source, var_field) do
source_var = get_variable(variable_source, id)
Map.put(propagator_var, var_field, Map.get(source_var, var_field))
end
def bind_to_variable(%View{variable: variable} = view, variable_source, var_field) do
bound_var = bind_to_variable(variable, variable_source, var_field)
Map.put(view, :variable, bound_var)
end
def bind_to_variable(const, _variable_source, _var_field) do
const
end
defp get_variable(%Graph{} = constraint_graph, var_id) do
ConstraintGraph.get_variable(constraint_graph, var_id)
end
defp get_variable(variable_source, var_id) when is_map(variable_source) do
Map.get(variable_source, var_id)
end
defp copy_variable(%Variable{domain: domain} = var) do
%{var | domain: Domain.copy(domain)}
end
defp copy_variable(%View{variable: variable} = view) do
Map.put(view, :variable, copy_variable(variable))
end
def is_constant_arg(%Variable{} = _arg) do
false
end
def is_constant_arg(%View{} = _arg) do
false
end
def is_constant_arg(_other) do
true
end
def arg_at(args, pos) when is_tuple(args) do
TupleArray.at(args, pos)
end
def arg_at(args, pos) do
Arrays.get(args, pos)
end
def arg_map(args, mapper) when is_function(mapper) and is_list(args) do
Enum.map(args, mapper)
end
def arg_map(args, mapper) when is_function(mapper) and is_tuple(args) do
TupleArray.map(args, mapper)
end
def arg_map(args, mapper) when is_function(mapper) do
Arrays.map(args, mapper)
end
def args_to_list(args) when is_tuple(args) do
Tuple.to_list(args)
end
def args_to_list(args) do
args
end
def domain_values(%{args: args} = _p) do
arg_map(args, fn arg ->
(is_constant_arg(arg) && arg) || {Interface.variable(arg).name, Utils.domain_values(arg)}
end)
end
defp copy_args(args) do
arg_map(args, fn arg ->
(is_constant_arg(arg) && arg) ||
copy_variable(arg)
end)
end
end