The algorithm interface
This section starts a single, cohesive story that will weave through all documentation pages. We will incrementally build an iterative algorithm, enrich it with stopping criteria, and finally refine how it records (logs) its progress. Instead of presenting the API in the abstract, we anchor every concept in one concrete, tiny example you can copy & adapt.
Why an “interface” for algorithms? Iterative numerical methods nearly always share the same moving pieces:
- immutable input (the mathematical problem you are solving),
- immutable configuration (parameters and knobs of the chosen algorithm), and
- mutable working data (current iterate, caches, diagnostics) that evolves as you step.
Bundling these together loosely without forcing one giant monolithic type makes it easier to:
- reason about what is allowed to change and what must remain fixed,
- write generic tooling (e.g. stopping logic, logging, benchmarking) that applies across many algorithms,
- test algorithms in isolation by constructing minimal
Problem/Algorithmpairs, and - extend behavior (add new stopping criteria, new logging events) without rewriting core loops.
The interface in this package formalizes those roles with three abstract types:
Problem: immutable, algorithm‑agnostic input data.Algorithm: immutable configuration and parameters deciding how to iterate.State: mutable data that evolves (current iterate, caches, counters, diagnostics).
It provides a framework for decomposing iterative methods into small, composable parts: concrete Problem/Algorithm/State types have to implement a minimal set of core functionality, and this package helps to stitch everything together and provide additional helper functionality such as stopping criteria and logging functionality.
Concrete example: Heron's method
To make everything tangible, we will work through a concrete example to illustrate the library's goals and concepts. Our running example is Heron's / Babylonian method for estimating $\sqrt{S}$. (see also the concise background on Wikipedia: Babylonian method (Heron's method)): Starting from an initial guess $x_0$, we may converge to the solution by iterating:
\[x_{k+1} = \frac{1}{2}\left(x_k + \frac{S}{x_k}\right)\]
We therefore suggest the following concrete implementations of the abstract types provided by this package: They are illustrative; various performance and generality questions will be left unaddressed to keep this example simple.
Algorithm types
using AlgorithmsInterface
struct SqrtProblem <: Problem
S::Float64 # number whose square root we seek
end
struct HeronAlgorithm <: Algorithm
stopping_criterion # will be plugged in later (any StoppingCriterion)
end
mutable struct HeronState <: State
iterate::Float64 # current iterate
iteration::Int # current iteration count
stopping_criterion_state # will be plugged in later (any StoppingCriterionState)
endInitialization
In order to begin implementing the core parts of our algorithm, we start at the very beginning. There are two main entry points provided by the interface:
initialize_stateconstructs an entirely new state for the algorithminitialize_state!(in-place) reset of an existing state.
An example implementation might look like:
function AlgorithmsInterface.initialize_state(
problem::SqrtProblem, algorithm::HeronAlgorithm,
stopping_criterion_state::StoppingCriterionState;
kwargs...
)
x0 = rand()
iteration = 0
return HeronState(x0, iteration, stopping_criterion_state)
end
function AlgorithmsInterface.initialize_state!(
problem::SqrtProblem, algorithm::HeronAlgorithm, state::HeronState;
kwargs...
)
state.iteration = 0
return state
endIteration steps
Algorithms define a mutable step via step!. For Heron's method:
function AlgorithmsInterface.step!(problem::SqrtProblem, algorithm::HeronAlgorithm, state::HeronState)
S = problem.S
x = state.iterate
state.iterate = 0.5 * (x + S / x)
return state
endNote that we are only focussing on the actual algorithm, and not incrementing the iteration counter. These kinds of bookkeeping should be handled by the AlgorithmsInterface.increment! function, which will by default already increment the iteration counter. The following generic functionality is therefore enough for our purposes, and does not need to be defined. Nevertheless, if additional bookkeeping would be desired, this can be achieved by overloading that function:
function AlgorithmsInterface.increment!(state::State)
state.iteration += 1
return state
endRunning the algorithm
With these definitions in place you can already run (assuming you also choose a stopping criterion – added in the next section):
function heron_sqrt(x; maxiter = 10)
prob = SqrtProblem(x)
alg = HeronAlgorithm(StopAfterIteration(maxiter))
state = solve(prob, alg) # allocates & runs
return state.iterate
end
println("Approximate sqrt: ", heron_sqrt(16.0))Approximate sqrt: 4.0We will refine this example with better halting logic and logging shortly.
Reference: Core interface types & functions
Below are the automatic API docs for the core interface pieces. Read them after grasping the example above – the intent should now be clearer.
AlgorithmsInterface.initialize_state! — Method
state = initialize_state(
problem::Problem, algorithm::Algorithm,
[stopping_criterion_state::StoppingCriterionState];
kwargs...
)
state = initialize_state!(
problem::Problem, algorithm::Algorithm, state::State;
kwargs...
)Initialize a State based on a Problem and an Algorithm. The kwargs... should allow to initialize for example the initial point. This can be done in-place for state, then only values that did change have to be provided.
Note that since the returned state should also hold state.stopping_criterion_state, which will be used to keep the internal state of the stopping criterion, the out-of-place version receives this as one of its arguments. By default, that will be initialized separately through a call to initialize_stopping_state and provided as an argument.
On the other hand, the in-place version is not responsible for initializing the stopping_criterion_state, as that will be handled separately by initialize_stopping_state!.
Users that which to handle the stopping criterion initialization in initialize_state manually should overload the 2-argument version, while by default the 3-argument version should be implemented.
AlgorithmsInterface.initialize_state — Method
state = initialize_state(
problem::Problem, algorithm::Algorithm,
[stopping_criterion_state::StoppingCriterionState];
kwargs...
)
state = initialize_state!(
problem::Problem, algorithm::Algorithm, state::State;
kwargs...
)Initialize a State based on a Problem and an Algorithm. The kwargs... should allow to initialize for example the initial point. This can be done in-place for state, then only values that did change have to be provided.
Note that since the returned state should also hold state.stopping_criterion_state, which will be used to keep the internal state of the stopping criterion, the out-of-place version receives this as one of its arguments. By default, that will be initialized separately through a call to initialize_stopping_state and provided as an argument.
On the other hand, the in-place version is not responsible for initializing the stopping_criterion_state, as that will be handled separately by initialize_stopping_state!.
Users that which to handle the stopping criterion initialization in initialize_state manually should overload the 2-argument version, while by default the 3-argument version should be implemented.
AlgorithmsInterface.solve! — Method
solve!(problem::Problem, algorithm::Algorithm, state::State; kwargs...)Solve the Problem using an Algorithm, starting from a given State. The state is modified in-place.
All keyword arguments are passed to the initialize_state! and initialize_stopping_state! functions.
AlgorithmsInterface.solve — Method
solve(problem::Problem, algorithm::Algorithm; kwargs...)Solve the Problem using an Algorithm.
The keyword arguments kwargs... have to provide enough details such that the corresponding state and stopping state initialisation initialize_stateand [initializestoppingstate`](@ref) can be used to return valid states and stopping states.
By default this method continues to call solve!.
AlgorithmsInterface.step! — Method
step!(problem::Problem, algorithm::Algorithm, state::State)Perform the current step of an Algorithm solving a Problem modifying the algorithm's State.
Algorithm
AlgorithmsInterface.Algorithm — Type
AlgorithmAn abstract type to represent an algorithm.
A concrete algorithm contains all static parameters that characterise the algorithms. Together with a Problem an Algorithm subtype should be able to initialize or reset a State.
Properties
Algorithms can contain any number of properties that are needed to define the algorithm, but should additionally contain the following properties to interact with the stopping criteria.
stopping_criterion::StoppingCriterion
Example
For a gradient descent algorithm the algorithm would specify which step size selection to use.
Problem
AlgorithmsInterface.Problem — Type
ProblemAn abstract type to represent a problem to be solved with all its static properties, that do not change during an algorithm run.
Example
For a gradient descent algorithm the problem consists of
- a
costfunction $f: C → ℝ$ - a gradient function $\operatorname{grad}f$
The problem then could that these are given in four different forms
- a function
c = cost(x)and a gradientd = gradient(x) - a function
c = cost(x)and an in-place gradientgradient!(d,x) - a combined cost-grad function
(c,d) = costgrad(x) - a combined cost-grad function
(c, d) = costgrad!(d, x)that computes the gradient in-place.
State
AlgorithmsInterface.State — Type
StateAn abstract type to represent the state an iterative algorithm is in.
The state consists of any information that describes the current step the algorithm is in and keeps all information needed from one step to the next.
Properties
In order to interact with the stopping criteria, the state should contain the following properties, and provide corresponding getproperty and setproperty! methods.
iteration– the current iteration step $k$ that is is currently performed or was last performedstopping_criterion_state– aStoppingCriterionStatethat indicates whether anAlgorithmwill stop after this iteration or has stopped.iteratethe current iterate $x^{(k)}$.
Methods
The following methods should be implemented for a state
increment!(state)
AlgorithmsInterface.increment! — Method
increment!(state::State)Increment the current iteration a State either is currently performing or was last performed
The default assumes that the current iteration is stored in state.iteration.
Stopping Criteria
AlgorithmsInterface.StoppingCriterion — Type
StoppingCriterionAn abstract type to represent a stopping criterion of an Algorithm.
A concrete StoppingCriterion should also implement a initialize_state(problem::Problem, algorithm::Algorithm, stopping_criterion::StoppingCriterion; kwargs...) function to create its accompanying StoppingCriterionState. as well as the corresponding mutating variant to reset such a StoppingCriterionState.
It should usually implement
indicates_convergence(stopping_criterion)indicates_convergence(stopping_criterion, stopping_criterion_state)is_finished!(problem, algorithm, state, stopping_criterion, stopping_criterion_state)is_finished(problem, algorithm, state, stopping_criterion, stopping_criterion_state)
AlgorithmsInterface.StoppingCriterionState — Type
StoppingCriterionStateAn abstract type to represent a stopping criterion state within a State. It represents the concrete state a StoppingCriterion is in.
It should usually implement
get_reason(stopping_criterion, stopping_criterion_state)indicates_convergence(stopping_criterion, stopping_criterion_state)is_finished!(problem, algorithm, state, stopping_criterion, stopping_criterion_state)is_finished(problem, algorithm, state, stopping_criterion, stopping_criterion_state)
AlgorithmsInterface.get_reason — Method
get_reason(stopping_criterion::StoppingCriterion, stopping_criterion_state::StoppingCriterionState)Provide a reason in human readable text as to why a StoppingCriterion with StoppingCriterionState indicated to stop. If it does not indicate to stop, this should return nothing.
Providing the iteration at which this indicated to stop in the reason would be preferable.
AlgorithmsInterface.indicates_convergence — Method
indicates_convergence(stopping_criterion::StoppingCriterion, ::StoppingCriterionState)Return whether or not a StoppingCriterion indicates convergence when it is in StoppingCriterionState.
By default this checks whether the StoppingCriterion has actually stopped. If so it returns whether stopping_criterion itself indicates convergence, otherwise it returns false, since the algorithm has then not yet stopped.
AlgorithmsInterface.indicates_convergence — Method
indicates_convergence(stopping_criterion::StoppingCriterion)Return whether or not a StoppingCriterion indicates convergence.
AlgorithmsInterface.initialize_stopping_state! — Method
stopping_criterion_state = initialize_stopping_state(
problem::Problem, algorithm::Algorithm
stopping_criterion::StoppingCriterion = algorithm.stopping_criterion;
kwargs...
)
stopping_criterion_state = initialize_stopping_state!(
problem::Problem, algorithm::Algorithm, state::State,
stopping_criterion::StoppingCriterion = algorithm.stopping_criterion,
stopping_criterion_state::StoppingCriterionState = state.stopping_criterion_state;
kwargs...
)Initialize a StoppingCriterionState based on a Problem, Algorithm, State triplet for a given StoppingCriterion. By default, the stopping_criterion is retrieved from the Algorithm via algorithm.stopping_criterion.
The first signature is used for setting up a completely new stopping criterion state, while the second simply resets a given state in-place.
AlgorithmsInterface.initialize_stopping_state — Method
stopping_criterion_state = initialize_stopping_state(
problem::Problem, algorithm::Algorithm
stopping_criterion::StoppingCriterion = algorithm.stopping_criterion;
kwargs...
)
stopping_criterion_state = initialize_stopping_state!(
problem::Problem, algorithm::Algorithm, state::State,
stopping_criterion::StoppingCriterion = algorithm.stopping_criterion,
stopping_criterion_state::StoppingCriterionState = state.stopping_criterion_state;
kwargs...
)Initialize a StoppingCriterionState based on a Problem, Algorithm, State triplet for a given StoppingCriterion. By default, the stopping_criterion is retrieved from the Algorithm via algorithm.stopping_criterion.
The first signature is used for setting up a completely new stopping criterion state, while the second simply resets a given state in-place.
AlgorithmsInterface.is_finished! — Method
is_finished(problem::Problem, algorithm::Algorithm, state::State)
is_finished(problem::Problem, algorithm::Algorithm, state::State, stopping_criterion::StoppingCriterion, stopping_criterion_state::StoppingCriterionState)
is_finished!(problem::Problem, algorithm::Algorithm, state::State)
is_finished!(problem::Problem, algorithm::Algorithm, state::State, stopping_criterion::StoppingCriterion, stopping_criterion_state::StoppingCriterionState)Indicate whether an Algorithm solving Problem is finished having reached a certain State. The variant with three arguments by default extracts the StoppingCriterion and its StoppingCriterionState and their actual checks are performed in the implementation with five arguments.
The mutating variant does alter the stopping_criterion_state and and should only be called once per iteration, the other one merely inspects the current status without mutation.
AlgorithmsInterface.is_finished! — Method
is_finished(problem::Problem, algorithm::Algorithm, state::State)
is_finished(problem::Problem, algorithm::Algorithm, state::State, stopping_criterion::StoppingCriterion, stopping_criterion_state::StoppingCriterionState)
is_finished!(problem::Problem, algorithm::Algorithm, state::State)
is_finished!(problem::Problem, algorithm::Algorithm, state::State, stopping_criterion::StoppingCriterion, stopping_criterion_state::StoppingCriterionState)Indicate whether an Algorithm solving Problem is finished having reached a certain State. The variant with three arguments by default extracts the StoppingCriterion and its StoppingCriterionState and their actual checks are performed in the implementation with five arguments.
The mutating variant does alter the stopping_criterion_state and and should only be called once per iteration, the other one merely inspects the current status without mutation.
AlgorithmsInterface.is_finished — Method
is_finished(problem::Problem, algorithm::Algorithm, state::State)
is_finished(problem::Problem, algorithm::Algorithm, state::State, stopping_criterion::StoppingCriterion, stopping_criterion_state::StoppingCriterionState)
is_finished!(problem::Problem, algorithm::Algorithm, state::State)
is_finished!(problem::Problem, algorithm::Algorithm, state::State, stopping_criterion::StoppingCriterion, stopping_criterion_state::StoppingCriterionState)Indicate whether an Algorithm solving Problem is finished having reached a certain State. The variant with three arguments by default extracts the StoppingCriterion and its StoppingCriterionState and their actual checks are performed in the implementation with five arguments.
The mutating variant does alter the stopping_criterion_state and and should only be called once per iteration, the other one merely inspects the current status without mutation.
AlgorithmsInterface.is_finished — Method
is_finished(problem::Problem, algorithm::Algorithm, state::State)
is_finished(problem::Problem, algorithm::Algorithm, state::State, stopping_criterion::StoppingCriterion, stopping_criterion_state::StoppingCriterionState)
is_finished!(problem::Problem, algorithm::Algorithm, state::State)
is_finished!(problem::Problem, algorithm::Algorithm, state::State, stopping_criterion::StoppingCriterion, stopping_criterion_state::StoppingCriterionState)Indicate whether an Algorithm solving Problem is finished having reached a certain State. The variant with three arguments by default extracts the StoppingCriterion and its StoppingCriterionState and their actual checks are performed in the implementation with five arguments.
The mutating variant does alter the stopping_criterion_state and and should only be called once per iteration, the other one merely inspects the current status without mutation.
Base.summary — Method
summary(io::IO, stopping_criterion::StoppingCriterion, stopping_criterion_state::StoppingCriterionState)Provide a summary of the status of a stopping criterion – its parameters and whether it currently indicates to stop. It should not be longer than one line
Example
For the StopAfterIteration criterion, the summary looks like
Max Iterations (15): not reachedNext: Stopping criteria
Proceed to the stopping criteria section to add robust halting logic (iteration caps, time limits, tolerance on successive iterates, and combinations) to this square‑root example.