Packages
fixpoint
0.5.4
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/solver.ex
defmodule CPSolver do
@moduledoc """
Solver API.
"""
alias CPSolver.Space
alias CPSolver.Constraint
alias CPSolver.Solution
alias CPSolver.Shared
use GenServer
require Logger
@default_timeout 30_000
@doc """
"""
@spec solve(Model.t(), Keyword.t()) :: any()
def solve(model, opts \\ []) do
shared_data = Shared.init_shared_data() |> Map.put(:sync_mode, opts[:sync_mode])
{:ok, solver_pid} =
GenServer.start(CPSolver, [model, Keyword.put(opts, :shared, shared_data)])
{:ok,
shared_data
|> Map.put(:solver_pid, solver_pid)
|> Map.put(:variable_names, Enum.map(model.variables, & &1.name))}
end
@spec solve_sync(Model.t(), Keyword.t()) ::
{:ok, map()} | {:error, reason :: any(), info :: any()}
def solve_sync(model, opts \\ []) do
{:ok, solver} = solve(model, Keyword.put(opts, :sync_mode, true))
:ok = wait_for_completion(solver, Keyword.get(opts, :timeout, @default_timeout))
get_results(solver)
|> tap(fn _ -> cleanup(solver) end)
end
defp wait_for_completion(%{complete_flag: complete_flag} = solver, timeout) do
receive do
{:solver_completed, ^complete_flag} -> :ok
after
timeout ->
Logger.error("Timeout waiting on solver completion")
CPSolver.set_complete(solver)
end
end
defp get_results(solver) do
{:ok,
%{
statistics: statistics(solver),
variables: solver.variable_names,
solutions: solutions(solver)
}}
end
def stop_spaces(solver) do
Shared.stop_spaces(solver)
end
defp cleanup(solver) do
Shared.cleanup(solver)
end
def statistics(solver) when is_pid(solver) do
GenServer.call(solver, :get_stats)
end
def statistics(solver) when is_map(solver) do
Shared.statistics(solver)
end
def solutions(solver) when is_pid(solver) do
GenServer.call(solver, :get_solutions)
end
def solutions(solver) when is_map(solver) do
Shared.solutions(solver)
end
def get_state(solver) when is_pid(solver) do
:sys.get_state(solver)
end
def get_state(solver) when is_map(solver) do
get_state(solver.solver_pid)
end
def complete?(solver) when is_map(solver) do
Shared.complete?(solver)
end
def set_complete(solver) do
Shared.set_complete(solver)
end
## GenServer callbacks
@impl true
def init([%{constraints: constraints, variables: variables} = _model, solver_opts]) do
propagators =
Enum.reduce(constraints, [], fn constraint, acc ->
acc ++ Constraint.constraint_to_propagators(constraint)
end)
stop_on = Keyword.get(solver_opts, :stop_on)
## Some data (stats, solutions, possibly more - TBD) has to be shared between spaces
shared = Keyword.get(solver_opts, :shared)
{:ok,
%{
space: nil,
variables: variables,
propagators: propagators,
shared: shared,
stop_on: stop_on,
solver_opts: Keyword.merge(Space.default_space_opts(), solver_opts)
}, {:continue, :solve}}
end
@impl true
def handle_continue(
:solve,
%{
variables: variables,
propagators: propagators,
solver_opts: solver_opts,
shared: shared
} = state
) do
solution_handler_fun =
solver_opts
|> Keyword.get(:solution_handler, Solution.default_handler())
|> build_solution_handler(state)
|> Solution.solution_handler(variables)
{:ok, top_space} =
Space.create(
variables,
propagators,
solver_opts
|> Keyword.put(:solver_data, shared)
|> Keyword.delete(:shared)
|> Keyword.put(:solution_handler, solution_handler_fun)
)
{:noreply, Map.put(state, :space, top_space)}
end
@impl true
def handle_info(event, state) do
{:noreply, handle_event(event, state)}
end
def handle_event(_event, state) do
state
end
## Build a solution handler on top of initial one.
## For now, this adds handling logic for stop conditions
defp build_solution_handler(solution_handler, solver_state) do
stop_on_opt = get_in(solver_state, [:solver_opts, :stop_on])
fn solution ->
if not CPSolver.complete?(solver_state.shared) do
solution
|> Solution.run_handler(solution_handler)
|> tap(fn _ -> Shared.add_solution(solver_state.shared, solution) end)
|> tap(fn result -> check_stop_condition(stop_on_opt, result, solution, solver_state) end)
end
end
end
defp check_stop_condition(stop_on_opt, handler_result, solution, solver_state) do
stop_on_opt &&
condition_fun(stop_on_opt).(handler_result, solution, solver_state) &&
Shared.set_complete(solver_state.shared)
end
defp condition_fun({:max_solutions, max_solutions}) do
fn _handler_result, _solution, solver_state ->
solution_count = Shared.statistics(solver_state.shared).solution_count
max_solutions <= solution_count
end
end
defp condition_fun(opts) do
Logger.error("Stop condition with #{inspect(opts)} is not implemented")
end
end