ExVrp.StoppingCriteria (ExVrp v0.4.2)

Copy Markdown View Source

Stopping criteria for controlling when the solver terminates.

This is a port of PyVRP's stopping criteria. In PyVRP, criteria are called with stop(best_cost) and return a boolean. We provide both the struct-based API and a to_stop_fn/1 that creates a closure matching PyVRP's interface.

Available Criteria

Example

# Stop after 1000 iterations OR 60 seconds
stop = StoppingCriteria.multiple_criteria([
  StoppingCriteria.max_iterations(1000),
  StoppingCriteria.max_runtime(60.0)
])

{:ok, result} = Solver.solve(model, stop: stop)

Summary

Functions

Creates a combined criterion that stops when ALL of the sub-criteria are met.

Alias for multiple_criteria/1 for convenience.

Creates a criterion that stops when a feasible solution is found.

Creates a combined criterion that stops when EITHER a feasible solution is found OR the other criterion is met.

Creates a criterion that stops after a maximum number of iterations.

Creates a criterion that stops after a maximum runtime in seconds.

Creates a combined criterion that stops when ANY of the sub-criteria are met.

Creates a criterion that stops after N iterations without improvement.

Checks if the stopping criterion has been met.

Converts a StoppingCriteria struct to a stop function matching PyVRP's interface.

Types

stop_fn()

@type stop_fn() :: (non_neg_integer() -> boolean())

t()

@type t() :: %ExVrp.StoppingCriteria{
  state: map(),
  type:
    :max_iterations
    | :max_runtime
    | :no_improvement
    | :multiple_criteria
    | :first_feasible
}

Functions

all(criteria)

@spec all([t()]) :: t()

Creates a combined criterion that stops when ALL of the sub-criteria are met.

Note: PyVRP's MultipleCriteria uses OR logic (any). This is an extension that uses AND logic (all must be met).

any(criteria)

@spec any([t()]) :: t()

Alias for multiple_criteria/1 for convenience.

first_feasible()

@spec first_feasible() :: t()

Creates a criterion that stops when a feasible solution is found.

This matches PyVRP's FirstFeasible class.

Example

StoppingCriteria.first_feasible()

first_feasible_or(other_criteria)

@spec first_feasible_or(t()) :: t()

Creates a combined criterion that stops when EITHER a feasible solution is found OR the other criterion is met.

This is useful for fleet minimisation where we want to stop early if we find a feasible solution, but also respect an overall stopping criterion.

Example

# Stop when feasible or after 1000 iterations, whichever comes first
stop = StoppingCriteria.first_feasible_or(StoppingCriteria.max_iterations(1000))

max_iterations(max)

@spec max_iterations(non_neg_integer()) :: t()

Creates a criterion that stops after a maximum number of iterations.

Raises ArgumentError if max_iterations is negative.

Example

StoppingCriteria.max_iterations(1000)

max_runtime(max_seconds)

@spec max_runtime(number()) :: t()

Creates a criterion that stops after a maximum runtime in seconds.

This matches PyVRP's MaxRuntime which takes seconds as a float.

Raises ArgumentError if max_runtime is negative.

Example

StoppingCriteria.max_runtime(60.0)  # 60 seconds

multiple_criteria(criteria)

@spec multiple_criteria([t()]) :: t()

Creates a combined criterion that stops when ANY of the sub-criteria are met.

This matches PyVRP's MultipleCriteria class.

Raises ArgumentError if the criteria list is empty.

Example

StoppingCriteria.multiple_criteria([
  StoppingCriteria.max_iterations(1000),
  StoppingCriteria.max_runtime(60.0)
])

no_improvement(max_no_improvement)

@spec no_improvement(non_neg_integer()) :: t()

Creates a criterion that stops after N iterations without improvement.

The counter resets whenever an improving solution is found, matching PyVRP's NoImprovement behavior.

Raises ArgumentError if max_iterations is negative.

Example

StoppingCriteria.no_improvement(100)  # Stop after 100 iterations without improvement

should_stop?(criteria, context)

@spec should_stop?(t(), map()) :: {boolean(), t()}

Checks if the stopping criterion has been met.

Returns {should_stop?, updated_criteria} where the updated criteria tracks any state changes (like iteration counts).

to_stop_fn(criteria)

@spec to_stop_fn(t()) :: stop_fn()

Converts a StoppingCriteria struct to a stop function matching PyVRP's interface.

The returned function takes best_cost and returns true to stop. Uses an Agent to maintain state across calls.

Example

criteria = StoppingCriteria.max_iterations(100)
stop_fn = StoppingCriteria.to_stop_fn(criteria)
stop_fn.(1000)  # => false (first call)
# ... after 100 calls ...
stop_fn.(1000)  # => true