defmodule CPSolver.Objective do alias CPSolver.Objective.Propagator, as: ObjectivePropagator alias CPSolver.Variable.Interface import CPSolver.Variable.View.Factory import CPSolver.Utils @spec minimize(Variable.t() | View.t()) :: %{ :propagator => Propagator.t(), :variable => Variable.t() | View.t(), :bound_handle => reference() } def minimize(variable) do bound_handle = init_bound_handle() propagator = ObjectivePropagator.new(variable, bound_handle) %{ propagator: propagator, variable: variable, bound_handle: bound_handle, target: :minimize } end def maximize(variable) do minimize(minus(variable)) |> Map.put(:target, :maximize) end def get_bound(%{bound_handle: handle}) do get_bound(handle) end def get_bound(handle) when is_reference(handle) do (on_primary_node?(handle) && get_bound_impl(handle)) || remote_call(handle, :get_bound_impl) end def get_bound_impl(handle) when is_reference(handle) do :atomics.get(handle, 1) end def update_bound(handle, value) when is_reference(handle) do (on_primary_node?(handle) && update_bound_impl(handle, value)) || remote_call(handle, :update_bound_impl, [value]) end def update_bound_impl(bound_handle, value) do case :atomics.get(bound_handle, 1) do prev_value when prev_value > value -> case :atomics.compare_exchange(bound_handle, 1, prev_value, value) do :ok -> value _changed_in_between -> update_bound_impl(bound_handle, value) end prev_value -> # previous value lesser than the proposed one - keep prev_value end end def tighten(%{variable: variable, bound_handle: handle} = _objective) do tighten(variable, handle) end def tighten(variable, bound_handle) do update_bound(bound_handle, Interface.max(variable) - 1) end def init_bound_handle() do ref = :atomics.new(1, signed: true) reset_bound(ref) ref end def reset_bound(%{bound_handle: ref} = _objective) do reset_bound(ref) end def reset_bound(handle) when is_reference(handle) do (on_primary_node?(handle) && reset_bound_impl(handle)) || remote_call(handle, :reset_bound_impl) end def reset_bound_impl(ref) when is_reference(ref) do :atomics.put(ref, 1, :atomics.info(ref).max) end def get_objective_value(%{target: target, bound_handle: handle} = _objective) do (get_bound(handle) + 1) * ((target == :minimize && 1) || -1) end defp remote_call(ref, fun_name, args \\ []) when is_reference(ref) and is_atom(fun_name) do :erpc.call(node(ref), __MODULE__, fun_name, [ref | args]) end end