Packages
fixpoint
0.7.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/shared.ex
defmodule CPSolver.Shared do
alias CPSolver.Objective
alias CPSolver.Variable.Interface
def init_shared_data(max_space_threads: max_space_threads) do
%{
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_counter: init_space_thread_counter(max_space_threads),
times: init_times()
}
end
def complete?(%{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
defp get_times(%{times: time_ref} = _solver) do
{_start_time, _end_time} = :persistent_term.get(time_ref)
end
defp set_end_time(%{times: ref} = solver) do
{start_time, _end_time} = get_times(solver)
:persistent_term.put(ref, {start_time, :erlang.monotonic_time()})
end
## First element is a thread counter, 2nd is the max number of
## space processes allowed to run simultaneously.
defp init_space_thread_counter(max_space_threads) do
ref = :counters.new(2, [:atomics])
:counters.put(ref, 1, 0)
:counters.put(ref, 2, max_space_threads)
ref
end
def get_space_thread_counter(%{space_thread_counter: counter_ref} = _shared) do
:counters.get(counter_ref, 1)
end
def checkout_space_thread(%{space_thread_counter: counter_ref} = _shared) do
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(%{space_thread_counter: counter_ref} = _shared) do
: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 add_active_spaces(
%{statistics: stats_table, active_nodes: active_nodes_table} = _solver,
spaces
) do
try do
incr = length(spaces)
update_stats_counters(stats_table, [{@active_node_count_pos, incr}, {@node_count_pos, incr}])
Enum.each(spaces, fn n -> :ets.insert(active_nodes_table, {n, n}) end)
rescue
_e -> :ok
end
end
def remove_space(
%{statistics: stats_table, active_nodes: active_nodes_table} = solver,
space,
reason
) do
try do
update_stats_counters(stats_table, [
{@active_node_count_pos, -1} | update_stats_ops(reason)
])
:ets.delete(active_nodes_table, space)
## The solving is done when there is no more active nodes
:ets.info(active_nodes_table, :size) == 0 && set_complete(solver)
rescue
_e -> :ok
end
end
def cleanup(
%{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)
objective && Objective.reset_bound_handle(objective)
end
def stop_spaces(solver) do
Enum.each(active_nodes(solver), fn space ->
Process.alive?(space) && Process.exit(space, :normal)
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
:ets.update_counter(stats_table, :stats, update_ops)
end
def statistics(solver) 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)
}
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(%{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
def add_failure(%{statistics: stats_table} = _solver) do
update_stats_counters(stats_table, [{@failure_count_pos, 1}])
end
def add_solution(
solution,
%{solutions: solution_table, statistics: stats_table, objective: objective_rec} = _solver
) 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 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
end