Packages
fixpoint
0.11.2
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
alias CPSolver.Common
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(Enum.to_list(args))
end
def reset(%{mod: mod, args: args} = propagator, opts \\ []) do
update_state(propagator, mod.reset(args, Map.get(propagator, :state), opts))
end
def update_state(propagator, state) do
Map.put(propagator, :state, state)
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} = propagator, opts \\ []) do
try do
propagator = maybe_reset_state(propagator, opts)
do_filter(propagator, Keyword.get(opts, :changes) || %{})
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
defp maybe_reset_state(%{mod: mod, args: args, state: state} = propagator, opts) do
## We will reset the state if required.
## Reset will be forced on all propagators when the space starts propagation.
Keyword.get(opts, :reset?)
&& Map.put(propagator, :state, mod.reset(args, state, opts))
|| propagator
end
defp positional_changes(domain_changes, positions_map) do
## Propagation changes is a var_ref => domain_change map
## For performance considerations, it has to be transformed to
## var_position => domain_change map,
## where `var_position is a position in propagator's argument list.
##
Enum.reduce(domain_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
defp do_filter(%{mod: mod, args: args, state: state, variable_positions: positions} = _propagator,
domain_changes) do
### The propagator filtering can return:
## - :fail
## Meaning the propagator thinks it has found inconsistencies
## (for instance, Circuit propagator concludes there is no possible way to have a hamiltonian cycle)
## given current variable domains
## - :stable
## Propagator claims that filtering resulted neither in variable domain changes nor
## propagator state.
##
## - :passive
## Propagator claims it won't be able to do further reductions
## of variable domains regardless of their current state.
## Note: in this case, the state of propagator is irrelevant, as it will be excluded
## from any further propagations.
##
## - {:state, new_state}
## Propagator has updated it's state as a result of filtering.
##
## -any other result
## The propagator didn't change it's state, but it's possible there were
## changes in variable domains.
##
incoming_changes = positional_changes(domain_changes, positions)
case mod.filter(args, state, incoming_changes) do
:fail ->
:fail
:stable ->
%{changes: %{}, state: state, active?: true}
result ->
case result do
:passive ->
%{active?: false, state: nil}
{:state, updated_state} ->
%{active?: Map.get(updated_state, :active?, true), state: updated_state}
_ ->
%{active?: true, state: state}
end
|> Map.put(:changes, reset_filter_changes() || %{})
end
end
def reset_filter_changes() do
PropagatorVariable.reset_variable_ops()
end
def get_filter_changes() do
PropagatorVariable.get_variable_ops() || %{}
end
def merge_changes(changes1, changes2) do
Map.merge(changes1, changes2,
fn _var_id, domain_change1, domain_change2 ->
Common.stronger_domain_change(domain_change1, domain_change2)
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
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(constraint_graph, var_id) do
ConstraintGraph.get_variable(constraint_graph, 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) when is_list(args) do
Enum.at(args, pos)
end
def arg_at(args, pos) do
(Enumerable.impl_for(args) &&
Arrays.get(args, pos)) ||
throw({:error, :unknown_type, args})
end
def arg_map(%{args: args} = _propagator, mapper) do
arg_map(args, mapper)
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
(Enumerable.impl_for(args) &&
Arrays.map(args, mapper)) ||
throw({:error, :unknown_type, args})
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