Skip to content

17. Observing and controlling a run

You can monitor a search, stop it from another thread or record its decisions without changing the algorithm. Pass a control and a tracer as the final argument to the run:

std::stop_source stop;
auto observer = [](const el::run_progress& progress) {
    (void)progress.evaluations; // also: iterations, evaluation_limit
};
el::run_control control{stop.get_token(), observer};
el::trace::memory_recorder<double> trace;

auto descent_search = descent.bind(tsp);
const auto observed = descent_search.run(
    descent_search.initial_solution(),
    rng,
    el::with(control, trace));

Control and progress. Call stop.request_stop() from any thread to request cancellation. Each runner checks the request through search_run::should_stop() and reports termination_reason::cancelled. The observer receives run_progress, with evaluations, iterations and any evaluation budget.

Tracing. A tracer receives typed events such as run_started, move_evaluated, move_accepted, incumbent_updated and run_finished. Other events describe local optima, neighborhood selection, visited solution hashes, and Tabu Search's aspiration, escapes and tenure changes. Solvers add run_context before each run. Without a tracer, event construction is removed at compile time.

Match the recorder to the cost type: memory_recorder<double> records this TSP's events, including events with no cost value. Pass a control, a tracer or both with el::with(control), el::with(tracer) or el::with(control, tracer).

Run limits. The same options work across algorithms:

Option Stops when Termination reason
el::stop_at(cost) the best cost reaches the target or improves on it target_reached
el::timeout(5s) or el::timeout(2.5) the time limit expires; numeric values are seconds time_limit_reached
el::max_evaluations(n) the evaluation budget is exhausted evaluation_budget_exhausted

Timeouts accept any std::chrono duration. The run checks the clock alongside its other stopping conditions; no timer thread is needed. An evaluation limit can tighten, but cannot raise, the runner's own budget.

Combine options as needed, for example el::with(control, tracer).timeout(5s).stop_at(cost). Solvers accept them too: solver.solve(input, el::with(control)). A solver's time and evaluation limits cover all its runs together. Pipeline stages can also have their own limits, such as & el::timeout(10s) or & el::max_evaluations(5000).

Use trace::memory_recorder to inspect events in code, trace::jsonl_recorder for JSON Lines, or binary_recorder and async_binary_recorder for ELTR files. scripts/eltr.py decodes ELTR traces; see Tracing.

Diagnostic logging (<easylocal/utils/logging.hpp>) is separate from tracing.

See also

Next steps

The reference describes every component in full; Coming from EasyLocal 3 maps EasyLocal 3 to this framework.