Packages
fixpoint
0.9.1
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/shared.ex
defmodule CPSolver.Shared do
alias CPSolver.Objective
alias CPSolver.Variable.Interface
alias CPSolver.Distributed
def init_shared_data(opts) do
distributed = Keyword.get(opts, :distributed, false)
space_threads = Keyword.get(opts, :space_threads)
%{
caller: self(),
sync_mode: false,
solver_pid: self(),
statistics:
:ets.new(__MODULE__, [:set, :public, read_concurrency: true, write_concurrency: false])
|> tap(fn stats_ref -> :ets.insert(stats_ref, {:stats, 0, 0, 0, 0}) end),
solutions:
:ets.new(__MODULE__, [:set, :public, read_concurrency: true, write_concurrency: true]),
active_nodes:
:ets.new(__MODULE__, [:set, :public, read_concurrency: true, write_concurrency: false]),
complete_flag: init_complete_flag(),
space_thread_counters: init_space_thread_counters(space_threads),
times: init_times(),
distributed: distributed
}
end
def complete?(solver) do
(on_primary_node?(solver) &&
complete_impl(solver)) ||
distributed_call(solver, :complete_impl)
end
def complete_impl(%{complete_flag: complete_flag} = _solver) do
:persistent_term.get(complete_flag, true)
end
def set_complete(%{complete_flag: complete_flag, caller: caller, sync_mode: sync?} = solver) do
:persistent_term.put(complete_flag, true)
set_end_time(solver)
|> tap(fn _ -> sync? && send(caller, {:solver_completed, complete_flag}) end)
|> tap(fn _ -> CPSolver.stop_spaces(solver) end)
end
## Elapsed time in microsecs
def elapsed_time(solver) do
{start_time, end_time} = get_times(solver)
(((end_time && end_time) || :erlang.monotonic_time()) - start_time)
|> div(1_000)
end
defp init_complete_flag() do
make_ref()
|> tap(fn ref -> :persistent_term.put(ref, false) end)
end
def init_times() do
make_ref()
|> tap(fn ref -> :persistent_term.put(ref, {:erlang.monotonic_time(), nil}) end)
end
def get_times(solver) do
(on_primary_node?(solver) &&
get_times_impl(solver)) ||
distributed_call(solver, :get_times_impl)
end
def get_times_impl(%{times: time_ref} = _solver) do
{_start_time, _end_time} = :persistent_term.get(time_ref)
end
def set_end_time(solver) do
(on_primary_node?(solver) &&
set_end_time_impl(solver)) ||
distributed_call(solver, :get_times_impl)
end
def set_end_time_impl(%{times: ref} = solver) do
{start_time, _end_time} = get_times(solver)
:persistent_term.put(ref, {start_time, :erlang.monotonic_time()})
end
## This is a map nodes => atomics
## The value is 2-element (:counters) array
## First element is a thread counter, 2nd is the max number of
## space processes allowed to run simultaneously on a given node.
defp init_space_thread_counters(space_threads, nodes \\ [Node.self() | Node.list()]) do
Map.new(nodes, fn node ->
ref = :counters.new(2, [:atomics])
:counters.put(ref, 1, 0)
:counters.put(ref, 2, space_threads)
{node, ref}
end)
|> then(fn node_thread_counters ->
make_ref()
|> tap(fn ref -> :persistent_term.put(ref, node_thread_counters) end)
end)
end
def get_space_thread_counter(
%{space_thread_counters: node_threads_ref} = _shared,
node \\ Node.self()
) do
counter_ref = :persistent_term.get(node_threads_ref) |> Map.get(node)
:counters.get(counter_ref, 1)
end
def checkout_space_thread(solver, node \\ Node.self()) do
(on_primary_node?(solver) &&
checkout_space_thread_impl(solver, node)) ||
distributed_call(solver, :checkout_space_thread_impl, [node])
end
def checkout_space_thread_impl(
%{space_thread_counters: node_threads_ref} = _solver,
node
) do
counter_ref = :persistent_term.get(node_threads_ref) |> Map.get(node)
if :counters.get(counter_ref, 1) < :counters.get(counter_ref, 2) do
:counters.add(counter_ref, 1, 1)
true
end
end
def checkin_space_thread(solver) do
(on_primary_node?(solver) &&
checkin_space_thread_impl(solver)) ||
distributed_call(solver, :checkin_space_thread_impl)
end
def checkin_space_thread_impl(
%{space_thread_counters: node_threads_ref} = _solver,
node \\ Node.self()
) do
counter_ref = :persistent_term.get(node_threads_ref) |> Map.get(node)
:counters.get(counter_ref, 1) > 0 && :counters.sub(counter_ref, 1, 1)
end
@active_node_count_pos 2
@failure_count_pos 3
@solution_count_pos 4
@node_count_pos 5
def on_primary_node?(%{solver_pid: solver_pid} = _solver) do
Node.self() == node(solver_pid)
end
def increment_node_counts(solver) do
(on_primary_node?(solver) &&
increment_node_counts_impl(solver)) ||
distributed_call(solver, :increment_node_counts_impl)
end
def increment_node_counts_impl(%{statistics: stats_table}) do
update_stats_counters(stats_table, [{@active_node_count_pos, 1}, {@node_count_pos, 1}])
end
def add_active_spaces(
solver,
spaces
) do
(on_primary_node?(solver) &&
add_active_spaces_impl(solver, spaces)) ||
distributed_call(solver, :add_active_spaces_impl, [spaces])
end
def add_active_spaces_impl(%{active_nodes: active_nodes_table} = _solver_state, spaces) do
try do
Enum.each(spaces, fn n -> :ets.insert(active_nodes_table, {n, n}) end)
rescue
_e -> :ok
end
end
def remove_space(solver, space, reason) do
(on_primary_node?(solver) &&
remove_space_impl(solver, space, reason)) ||
distributed_call(solver, :remove_space_impl, [space, reason])
end
def remove_space_impl(
%{statistics: stats_table, active_nodes: active_nodes_table} = solver,
space,
reason
) do
try do
[active_node_count | _] =
update_stats_counters(stats_table, [
{@active_node_count_pos, -1, 0, 0} | update_stats_ops(reason)
])
:ets.delete(active_nodes_table, space)
## The solving is done when there is no more active nodes
active_node_count == 0 && set_complete(solver)
:ok
rescue
_e -> :ok
end
end
def cleanup(solver) do
(on_primary_node?(solver) &&
cleanup_impl(solver)) ||
distributed_call(solver, :cleanup_impl)
end
def cleanup_impl(
%{solver_pid: solver_pid, complete_flag: complete_flag, objective: objective} = solver
) do
Enum.each([:solutions, :statistics, :active_nodes], fn item ->
Map.get(solver, item) |> :ets.delete()
end)
Process.alive?(solver_pid) && GenServer.stop(solver_pid)
:persistent_term.erase(complete_flag)
reset_objective(objective)
:ok
end
def stop_spaces(solver) do
Enum.each(active_nodes(solver), fn space ->
:erpc.cast(node(space), fn -> Process.alive?(space) && Process.exit(space, :normal) end)
end)
end
def add_failure(solver) do
(on_primary_node?(solver) &&
add_failure_impl(solver)) ||
distributed_call(solver, :add_failure_impl)
end
def add_failure_impl(%{statistics: stats_table} = _solver) do
update_stats_counters(stats_table, [{@failure_count_pos, 1}])
end
def add_solution(solver, solution) do
(on_primary_node?(solver) &&
add_solution_impl(solver, solution)) ||
distributed_call(solver, :add_solution_impl, [solution])
end
def add_solution_impl(
%{solutions: solution_table, statistics: stats_table, objective: objective_rec} = _solver,
solution
) do
try do
update_stats_counters(stats_table, [{@solution_count_pos, 1}])
:ets.insert(
solution_table,
{make_ref(),
%{
solution: Enum.map(solution, fn {_var_id, value} -> value end),
objective_value:
objective_rec && objective_value_from_solution(solution, objective_rec)
}}
)
rescue
_e -> :ok
end
end
defp update_stats_ops(:failure) do
[{@failure_count_pos, 1}]
end
defp update_stats_ops(:solved) do
[]
end
defp update_stats_ops(_) do
[]
end
defp update_stats_counters(stats_table, update_ops) do
try do
:ets.update_counter(stats_table, :stats, update_ops)
rescue
_e -> []
end
end
defp reset_objective(objective) do
objective && Objective.reset_bound(objective)
end
def statistics(solver) do
(on_primary_node?(solver) &&
statistics_impl(solver)) ||
distributed_call(solver, :statistics_impl)
end
def statistics_impl(solver) do
try do
[{:stats, active_node_count, failure_count, solution_count, node_count}] =
:ets.lookup(solver.statistics, :stats)
%{
active_node_count: active_node_count,
failure_count: failure_count,
solution_count: solution_count,
node_count: node_count,
elapsed_time: elapsed_time(solver)
}
rescue
_e ->
%{}
end
end
def solutions(%{solutions: solution_table} = _solver) do
try do
solution_table
|> :ets.tab2list()
## Sort solutions by the objective value (the best solution is placed last)
|> Enum.sort_by(
fn {_ref, %{objective_value: objective_value}} ->
objective_value
end,
:desc
)
|> Enum.map(fn {_ref, %{solution: solution}} -> solution end)
rescue
_e -> []
end
end
def objective_value(%{objective: nil} = _solver) do
nil
end
def objective_value(solver) do
(on_primary_node?(solver) &&
objective_value_impl(solver)) ||
distributed_call(solver, :objective_value_impl)
end
def objective_value_impl(%{objective: objective_record} = _solver) do
Objective.get_objective_value(objective_record)
end
def active_nodes(%{active_nodes: active_nodes_table} = _solver) do
try do
:ets.tab2list(active_nodes_table) |> Enum.map(fn {_k, n} -> n end)
rescue
_e -> []
end
end
defp objective_value_from_solution(solution, %{variable: objective_variable} = _objective_rec) do
obj_var = Interface.variable(objective_variable)
Enum.find_value(solution, fn {var_name, value} ->
var_name == obj_var.name && Interface.map(objective_variable, value)
end)
end
defp distributed_call(%{solver_pid: solver_pid} = solver, function, args \\ []) do
Distributed.call(node(solver_pid), solver, __MODULE__, function, args)
end
end