Packages
fixpoint
0.14.7
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/search/strategy/variable/shared/afc.ex
defmodule CPSolver.Search.VariableSelector.AFC do
@moduledoc """
Accumulated failure count variable selector
(https://www.gecode.org/doc-latest/MPG.pdf, p.8.5.2)
"""
use CPSolver.Search.VariableSelector
alias CPSolver.Space
alias CPSolver.Shared
alias CPSolver.Propagator.ConstraintGraph
alias CPSolver.Variable.Interface
alias CPSolver.Utils
@impl true
def select(variables, data, opts) do
select_impl(variables, data, opts[:mode])
|> Enum.map(fn {var, _afc} -> var end)
end
defp select_impl(variables, data, :afc_min) do
Utils.minimals(
variable_afcs(variables, Space.get_shared(data)),
fn {_var, afc} -> afc end
)
end
defp select_impl(variables, data, :afc_max) do
Utils.maximals(
variable_afcs(variables, Space.get_shared(data)),
fn {_var, afc} -> afc end
)
end
defp select_impl(variables, data, :afc_size_min) do
Utils.minimals(
variable_afcs(variables, Space.get_shared(data)),
fn {var, afc} -> afc / Interface.size(var) end
)
end
defp select_impl(variables, data, :afc_size_max) do
Utils.maximals(
variable_afcs(variables, Space.get_shared(data)),
fn {var, afc} ->
afc / Interface.size(var)
end
)
end
@doc """
Initialize AFC
"""
@impl true
def initialize(space_data, opts) do
shared = Space.get_shared(space_data)
decay = opts[:decay]
Shared.get_auxillary(shared, :afc) ||
(
afc_table = Shared.create_shared_ets_table(shared)
Shared.put_auxillary(shared, :afc, %{propagator_afcs: afc_table, decay: decay})
Shared.add_handler(shared, :on_failure,
fn solver, {:fail, propagator_id} = _failure, failure_count ->
Shared.complete?(solver) ||
update_afc(propagator_id, solver, true, failure_count)
end
)
)
end
@doc """
Compute AFCs of variables in one pass
"""
def variable_afcs(variables, shared) do
graph = Shared.get_auxillary(shared, :initial_constraint_graph)
afc_data = Shared.get_auxillary(shared, :afc)
global_failure_count = Shared.get_failure_count(shared)
if graph && afc_data && global_failure_count do
%{propagator_afcs: afc_table, decay: decay} = afc_data
{propagator_ids, propagators_by_variable} =
Enum.reduce(variables, {MapSet.new(), Map.new()}, fn var, {propagator_ids_acc, map_acc} ->
p_ids = ConstraintGraph.get_propagator_ids(graph, Interface.id(var))
{
MapSet.union(propagator_ids_acc, MapSet.new(p_ids)),
Map.put(map_acc, var, p_ids)
}
end)
## Get p_id => afc_record map from ETS table
afc_records =
:ets.select(afc_table, for(p_id <- propagator_ids, do: {{p_id, :_}, [], [:"$_"]}))
|> Map.new()
## Collect variable AFCs
Enum.map(propagators_by_variable, fn {var, var_propagator_ids} ->
{var,
Enum.reduce(var_propagator_ids, 0, fn p_id, sum_acc ->
sum_acc +
(afc_records
|> Map.get(p_id, afc_record(1, 0))
|> propagator_afc(decay, global_failure_count)
|> elem(0))
end)}
end)
else
Enum.map(variables, fn var -> {var, 1} end)
end
end
@doc """
Compute AFC of variable based on the initial constraint graph.
"""
def variable_afc(variable_id, shared) when is_reference(variable_id) do
shared
|> Shared.get_auxillary(:initial_constraint_graph)
|> then(fn graph ->
(graph &&
ConstraintGraph.get_propagator_ids(graph, variable_id)) || []
end)
|> afc_sum(shared)
end
def variable_afc(variable, shared) do
variable_afc(Interface.id(variable), shared)
end
defp afc_sum(propagator_ids, shared) do
case Shared.get_auxillary(shared, :afc) do
%{propagator_afcs: afc_table, decay: decay} ->
global_failure_count = Shared.get_failure_count(shared)
propagator_records =
:ets.select(afc_table, for(p_id <- propagator_ids, do: {{p_id, :_}, [], [:"$_"]}))
## We add the count for not recorded propagators (the ones that did not have failures yet)
not_recorded_count = length(propagator_ids) - length(propagator_records)
not_recorded_decay =
(propagator_afc(afc_record(1, 0), decay, global_failure_count) |> elem(0)) *
not_recorded_count
Enum.reduce(propagator_records, not_recorded_decay, fn {_p_id, afc_record}, sum_acc ->
sum_acc +
(propagator_afc(afc_record, decay, global_failure_count) |> elem(0))
end)
_ ->
0
end
end
@doc """
Compute AFC based on last AFC value, decay and current global failure count.
This is (to be) used:
- for computing variable AFC;
- for updating AFC of a failed propagator;
- for updating AFCs of all propagators in case decay value has been dynamically changed.
"""
def propagator_afc(
{afc_value, last_update_at} = _afc_record,
decay,
global_failure_count,
failure? \\ false
)
when decay > 0 and decay <= 1 do
## Catch up on decaying (we do not update non-failing propagators on failure event!)
## We also assume that total failure count includes the last failure across the search nodes.
decay_steps =
max(
0,
if failure? do
global_failure_count - last_update_at - 1
else
global_failure_count - last_update_at
end
)
## It's impractical to consider a lot of decaying steps.
## Considering afc <- afc * decay formula, the AFC values will be very
## close to 0 for a small number of decays even if global failure count is high.
# We land on 100 as max for decay steps.
max_decay_steps = 100
## Add 1 to decayed AFC of failing propagator
new_afc_value =
afc_value * :math.pow(decay, min(max_decay_steps, decay_steps)) + ((failure? && 1) || 0)
{new_afc_value, global_failure_count}
end
def propagator_afc(propagator_id, shared) do
%{propagator_afcs: afc_table, decay: decay} = Shared.get_auxillary(shared, :afc)
propagator_afc(
get_afc_record(afc_table, propagator_id),
decay,
Shared.get_failure_count(shared)
)
end
defp afc_record(afc_value, last_failure_at) do
{afc_value, last_failure_at}
end
## Get AFC propagator record
def get_afc_record(table, propagator_id)
when is_reference(table) and is_reference(propagator_id) do
table
|> :ets.lookup(propagator_id)
|> then(fn rec ->
(!Enum.empty?(rec) && elem(hd(rec), 1)) ||
afc_record(1, 0) |> tap(fn rec -> :ets.insert(table, {propagator_id, rec}) end)
end)
end
def get_afc_record(propagator_id, shared) do
shared
|> get_afc_table()
|> get_afc_record(propagator_id)
end
def get_afc_table(shared) do
Shared.get_auxillary(shared, :afc)
|> Map.get(:propagator_afcs)
end
def get_decay(shared) do
Shared.get_auxillary(shared, :afc)
|> Map.get(:decay)
end
## Update AFC in 'shared'
def update_afc(propagator_id, shared, failure?, global_failure_count \\ nil) do
case Shared.get_auxillary(shared, :afc) do
nil -> :ok
%{propagator_afcs: afc_table, decay: decay} ->
failure_count = global_failure_count || Shared.get_failure_count(shared)
updated_record =
case get_afc_record(afc_table, propagator_id) do
nil ->
propagator_afc(afc_record(1, 0), decay, failure_count, failure?)
afc_record ->
propagator_afc(afc_record, decay, failure_count, failure?)
end
:ets.insert(afc_table, {propagator_id, updated_record})
end
end
end