Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ All notable Changes to the Julia package `AlgorithmsInterface.jl` are documented
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.0] 2026/05/01
## [0.1.0] 2026-05-01

Initial release.
Initial release.
48 changes: 18 additions & 30 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
# 🧮 AlgorithmsInterface.jl

`AlgorithmsInterface.jl` is a Julia package to provide a common interface to run iterative tasks.
**Algorithm** here refers to an iterative sequence of commands, that are run until a certain stopping criterion is met.
A small, composable interface for iterative algorithms in Julia.

[![docs][docs-dev-img]][docs-dev-url] [![CI][ci-img]][ci-url] [![runic][runic-img]][runic-url] [![codecov][codecov-img]][codecov-url] [![aqua][aqua-img]][aqua-url]


## Design

Iterative methods tend to share the same moving parts, which can lead to quite a bit of boilerplate and friction when trying to compose them.
This package aims to provide abstractions such as the main loop, stopping criteria, and a logging system shared by these methods.
It does not ship any concrete algorithms; the goal is to provide the tools to build on.
It does however ship with a useful set of stopping-criterion and logging primitives out of the box.

The surface is intentionally small.
The main design goal of the interface is to cleanly separate the implementation of the algorithm itself from the generic tools that surround it.
Those generic tools, such as stopping, logging and debugging, are written once and then work across every algorithm that adopts the interface.

See the [documentation][docs-dev-url] for the design walk-through, the API reference, and a worked example.
For background and discussion, see the [initial discussion](https://github.com/JuliaManifolds/AlgorithmsInterface.jl/discussions/1).

Note that this package is still in its design phase, and while SemVer is respected, (breaking) changes might still occur as the design takes shape.

[docs-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg
[docs-dev-url]: https://JuliaManifolds.github.io/AlgorithmsInterface.jl/dev/

Expand All @@ -19,31 +35,3 @@

[aqua-img]: https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg
[aqua-url]: https://github.com/JuliaTesting/Aqua.jl

# Statement of need

A first approach to algorithms is a simple for-loop for a maximum number of iterations.
Using an interface instead allows to both specify different criteria to stop easily, even in their combination.
Furthermore a generic interface allows to both “hook into” an algorithm easily as well as combining them.

A common interface for algorithms allows to reuse common code – especially stopping criteria, but especially also logging, debug, recording, and caching capabilities.
Finally, a common interface also allows to easily combine existing algorithms, hence enhancing interoperability, for example using one algorithm as a sub routine of another one.

# Main features

See the [initial discussion](https://github.com/JuliaManifolds/AlgorithmsInterface.jl/discussions/1)
as well as the [overview on existing things](https://github.com/JuliaManifolds/AlgorithmsInterface.jl/discussions/2)

## Further ideas

* generic stopping criteria `<:AbstractStoppingCriterion`
* `StopAfterIteration(i)` for example
* a factory that turns certain keywords like `maxiter=` into stopping criteria
* still support the `stopping_criterion=` ideas from `Manopt.jl`
* by default `stop()` from above would check such a stopping criterion
* generic debug and record functionality – together with hooks even

## Possible extensions

* to `LineSearches.jl`
*
2 changes: 1 addition & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AlgorithmsInterface.jl

Welcome to the Documentation of `AlgorithmsInterface.jl`.
Welcome to the documentation of `AlgorithmsInterface.jl`.

```@meta
CurrentModule = AlgorithmsInterface
Expand Down
3 changes: 2 additions & 1 deletion docs/src/interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ The interface in this package formalizes those roles with three abstract types:
* [`Problem`](@ref): immutable, algorithm‑agnostic input data.
* [`Algorithm`](@ref): immutable configuration and parameters deciding how to iterate.
* [`State`](@ref): 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.
Expand Down Expand Up @@ -86,7 +87,7 @@ function AlgorithmsInterface.initialize_state!(problem::SqrtProblem, algorithm::
# reset the state for the algorithm
state.iterate = rand()
state.iteration = 0

# reset the state for the stopping criterion
state = AlgorithmsInterface.initialize_state!(
problem, algorithm, algorithm.stopping_criterion, state.stopping_criterion_state
Expand Down
36 changes: 18 additions & 18 deletions docs/src/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ The logging system aims to achieve these goals by separating the logging logic i
These parts can be roughly described as *events* and *actions*, where the logging system is responsible for mapping between them.
Concretely, we have:

* **When do we log?** → an [`with_algorithmlogger`](@ref) to control how to map events to actions.
* **What happens when we log?** → a [`LoggingAction`](@ref) to determine what to do when an event happens.
* **When do we log?** → [`with_algorithmlogger`](@ref) controls how events are mapped to actions.
* **What happens when we log?** → a [`LoggingAction`](@ref) determines what to do when an event happens.

This separation allows users to compose rich behaviors (printing, collecting statistics, plotting) without modifying algorithm code, and lets algorithm authors emit domain‑specific events.

Expand Down Expand Up @@ -316,18 +316,18 @@ Inside the `solve!` function, logging events are emitted at key points:
function solve!(problem::Problem, algorithm::Algorithm, state::State; kwargs...)
initialize_state!(problem, algorithm, state; kwargs...)
emit_message(problem, algorithm, state, :Start)

while !is_finished!(problem, algorithm, state)
emit_message(problem, algorithm, state, :PreStep)

increment!(state)
step!(problem, algorithm, state)

emit_message(problem, algorithm, state, :PostStep)
end

emit_message(problem, algorithm, state, :Stop)

return finalize_state!(problem, algorithm, state)
end
```
Expand Down Expand Up @@ -405,9 +405,9 @@ function AlgorithmsInterface.step!(problem::SqrtProblem, algorithm::HeronAlgorit
# Suppose we check for numerical issues
if !isfinite(state.iterate) || mod(state.iteration, 10) == 0
emit_message(problem, algorithm, state, :Restart)
state.iterate = rand() # Reset the iterate an try again
state.iterate = rand() # Reset the iterate and try again
end

# Normal step
S = problem.S
x = state.iterate
Expand Down Expand Up @@ -438,7 +438,7 @@ nothing # hide

### Performance considerations

* Logging actions may be fast or slow, since the overhead is only incurred when actually using them.
* Logging actions can be as fast or slow as needed; the overhead is only incurred when they are actually used.
* Algorithms should be mindful of emitting events in hot loops. These events incur an overhead similar to accessing a `ScopedValue` (~10-100 ns), even when no logging action is registered.
* For expensive operations (plotting, I/O), it is often better to collect data during iteration and process afterward.
* Use `set_global_logging_state!(false)` for production benchmarks.
Expand Down Expand Up @@ -466,18 +466,18 @@ When designing custom logging contexts for your algorithms:
Implementing logging involves three main components:

1. **LoggingAction**: Define what happens when a logging event occurs.
- Use `CallbackAction` for quick inline functions.
- Implement custom subtypes for reusable, stateful logging.
- Implement `handle_message!(action, problem, algorithm, state; kwargs...)`.
* Use `CallbackAction` for quick inline functions.
* Implement custom subtypes for reusable, stateful logging.
* Implement `handle_message!(action, problem, algorithm, state; kwargs...)`.

2. **AlgorithmLogger**: Map contexts (`:Start`, `:PostStep`, etc.) to actions.
- Construct with `with_algorithmlogger(:Context => action, ...)`.
- Use `ActionGroup` to compose multiple actions at one context.
* Construct with `with_algorithmlogger(:Context => action, ...)`.
* Use `ActionGroup` to compose multiple actions at one context.

3. **Custom contexts**: Emit domain-specific events from algorithms.
- Call `emit_message(problem, algorithm, state, :YourContext)`.
- Document custom contexts in your algorithm's documentation.
- Use descriptive symbol names.
* Call `emit_message(problem, algorithm, state, :YourContext)`.
* Document custom contexts in your algorithm's documentation.
* Use descriptive symbol names.

The logging system is designed for composability and zero-overhead when disabled, letting you instrument algorithms without compromising performance or code clarity.

Expand Down
42 changes: 21 additions & 21 deletions docs/src/stopping_criterion.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The package ships several concrete [`StoppingCriterion`](@ref)s:

Each criterion has an associated [`StoppingCriterionState`](@ref) storing dynamic data (iteration when met, elapsed time, etc.).

Recall our [example implementation](@ref sec_heron) for Heron's method, where we we added a `stopping_criterion` to the `Algorithm`, as well as a `stopping_criterion_state` to the `State`.
Recall our [example implementation](@ref sec_heron) for Heron's method, where we added a `stopping_criterion` to the `Algorithm`, as well as a `stopping_criterion_state` to the `State`.

```@example Heron
using AlgorithmsInterface
Expand Down Expand Up @@ -134,7 +134,7 @@ criterion = StopAfterIteration(25) | StopAfter(Millisecond(50)) # logical OR
heron_sqrt(2; stopping_criterion = criterion)
```

Conversely, to demand both a minimum iteration quality condition **and** a cap, use `&` (logical AND).
Conversely, to demand both a minimum iteration count **and** a time cap, use `&` (logical AND).

```@example Heron
criterion = StopAfterIteration(25) & StopAfter(Millisecond(50)) # logical AND
Expand Down Expand Up @@ -194,30 +194,30 @@ Here, the mutating version alters the `stopping_criterion_state`, and should the
function AlgorithmsInterface.is_finished!(
::Problem, ::Algorithm, state::State, c::StopWhenStable, st::StopWhenStableState
)
k = state.iteration
if k == 0
st.previous_iterate = state.iterate
st.at_iteration = -1
return false
end

st.delta = abs(state.iterate - st.previous_iterate)
st.previous_iterate = state.iterate
if st.delta < c.tol
st.at_iteration = k
return true
end
return false
k = state.iteration
if k == 0
st.previous_iterate = state.iterate
st.at_iteration = -1
return false
end

st.delta = abs(state.iterate - st.previous_iterate)
st.previous_iterate = state.iterate
if st.delta < c.tol
st.at_iteration = k
return true
end
return false
end

function AlgorithmsInterface.is_finished(
::Problem, ::Algorithm, state::State, c::StopWhenStable, st::StopWhenStableState
)
k = state.iteration
k == 0 && return false
k = state.iteration
k == 0 && return false

Δ = abs(state.iterate - st.previous_iterate)
return Δ < c.tol
Δ = abs(state.iterate - st.previous_iterate)
return Δ < c.tol
end
```

Expand All @@ -244,7 +244,7 @@ criterion = StopWhenStable(1e-8)
heron_sqrt(16.0; stopping_criterion = criterion)
```

Note that our work payd off, as we can still compose this stopping criterion with other criteria as well:
Note that our work paid off, as we can still compose this stopping criterion with other criteria as well:

```@example Heron
criterion = StopWhenStable(1e-8) | StopAfterIteration(5)
Expand Down
2 changes: 1 addition & 1 deletion src/interface/algorithm.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

An abstract type to represent an algorithm.

A concrete algorithm contains all static parameters that characterise the algorithms.
A concrete algorithm contains all static parameters that characterise the algorithm.
Together with a [`Problem`](@ref) an `Algorithm` subtype should be able to initialize
or reset a [`State`](@ref).

Expand Down
1 change: 0 additions & 1 deletion src/interface/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ function initialize_state! end
@doc "$(_doc_init_state)"
initialize_state!(::Problem, ::Algorithm, ::State; kwargs...)


"""
output = finalize_state!(problem::Problem, algorithm::Algorithm, state::State)

Expand Down
2 changes: 1 addition & 1 deletion src/interface/problem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ For a [gradient descent](https://en.wikipedia.org/wiki/Gradient_descent) algorit
* a `cost` function ``f: C → ℝ``
* a gradient function ``$(raw"\operatorname{grad}")f``

The problem then could that these are given in four different forms
The problem could specify these in four different forms:

* a function `c = cost(x)` and a gradient `d = gradient(x)`
* a function `c = cost(x)` and an in-place gradient `gradient!(d,x)`
Expand Down
6 changes: 3 additions & 3 deletions src/interface/state.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ and keeps all information needed from one step to the next.
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 performed
* `iteration` – the current iteration step ``k`` that is currently being performed or was last performed.
* `stopping_criterion_state` – a [`StoppingCriterionState`](@ref) that indicates whether an [`Algorithm`](@ref)
will stop after this iteration or has stopped.
* `iterate` the current iterate ``x^{(k)}``.
* `iterate` the current iterate ``x^{(k)}``.

## Methods

Expand All @@ -27,7 +27,7 @@ abstract type State end
"""
increment!(state::State)

Increment the current iteration a [`State`](@ref) either is currently performing or was last performed
Increment the current iteration that a [`State`](@ref) is currently performing or was last performing.

The default assumes that the current iteration is stored in `state.iteration`.
"""
Expand Down
Loading
Loading