diff --git a/Changelog.md b/Changelog.md index 25c41db..973e013 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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. \ No newline at end of file +Initial release. diff --git a/Readme.md b/Readme.md index 9c3749b..2e0e424 100644 --- a/Readme.md +++ b/Readme.md @@ -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/ @@ -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` -* diff --git a/docs/src/index.md b/docs/src/index.md index 4f2fe61..0b3e5a6 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,6 +1,6 @@ # AlgorithmsInterface.jl -Welcome to the Documentation of `AlgorithmsInterface.jl`. +Welcome to the documentation of `AlgorithmsInterface.jl`. ```@meta CurrentModule = AlgorithmsInterface diff --git a/docs/src/interface.md b/docs/src/interface.md index 4ec3a27..461b76d 100644 --- a/docs/src/interface.md +++ b/docs/src/interface.md @@ -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. @@ -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 diff --git a/docs/src/logging.md b/docs/src/logging.md index b2b5da1..dcd6855 100644 --- a/docs/src/logging.md +++ b/docs/src/logging.md @@ -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. @@ -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 ``` @@ -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 @@ -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. @@ -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. diff --git a/docs/src/stopping_criterion.md b/docs/src/stopping_criterion.md index 7ffc475..39c2f50 100644 --- a/docs/src/stopping_criterion.md +++ b/docs/src/stopping_criterion.md @@ -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 @@ -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 @@ -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 ``` @@ -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) diff --git a/src/interface/algorithm.jl b/src/interface/algorithm.jl index 722d054..8998910 100644 --- a/src/interface/algorithm.jl +++ b/src/interface/algorithm.jl @@ -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). diff --git a/src/interface/interface.jl b/src/interface/interface.jl index f294dce..f37d513 100644 --- a/src/interface/interface.jl +++ b/src/interface/interface.jl @@ -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) diff --git a/src/interface/problem.jl b/src/interface/problem.jl index 291f875..e842af4 100644 --- a/src/interface/problem.jl +++ b/src/interface/problem.jl @@ -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)` diff --git a/src/interface/state.jl b/src/interface/state.jl index a0b3601..0d61775 100644 --- a/src/interface/state.jl +++ b/src/interface/state.jl @@ -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 @@ -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`. """ diff --git a/src/stopping_criterion.jl b/src/stopping_criterion.jl index 333b0a9..26a7dfa 100644 --- a/src/stopping_criterion.jl +++ b/src/stopping_criterion.jl @@ -3,10 +3,9 @@ An abstract type to represent a stopping criterion of an [`Algorithm`](@ref). -A concrete [`StoppingCriterion`](@ref) should also implement a +A concrete [`StoppingCriterion`](@ref) should also implement an [`initialize_state(problem::Problem, algorithm::Algorithm, stopping_criterion::StoppingCriterion; kwargs...)`](@ref) function to create its accompanying -[`StoppingCriterionState`](@ref). -as well as the corresponding mutating variant to reset such a [`StoppingCriterionState`](@ref). +[`StoppingCriterionState`](@ref), as well as the corresponding mutating variant to reset such a [`StoppingCriterionState`](@ref). It should usually implement @@ -77,7 +76,7 @@ a certain [`State`](@ref). The variant with three arguments by default extracts [`StoppingCriterion`](@ref) and its [`StoppingCriterionState`](@ref) 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 +The mutating variant does alter the `stopping_criterion_state` and should only be called once per iteration, the other one merely inspects the current status without mutation. """ @@ -108,7 +107,8 @@ is_finished!(::Problem, ::Algorithm, ::State, ::StoppingCriterion, ::StoppingCri summary(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.ìo` and generates a string from that. +it currently indicates to stop. The first variant prints the summary to `io`, +the second returns it as a string. # Example @@ -135,13 +135,13 @@ end @doc raw""" StopWhenAll <: StoppingCriterion -store a tuple of [`StoppingCriterion`](@ref)s and indicate to stop, -when _all_ indicate to stop. +Store a tuple of [`StoppingCriterion`](@ref)s and indicate to stop +when _all_ of them indicate to stop. # Constructor - StopWhenAll(c::NTuple{N,StoppingCriterion} where N) - StopWhenAll(c::StoppingCriterion,...) + StopWhenAll(c::NTuple{N, StoppingCriterion} where {N}) + StopWhenAll(c::StoppingCriterion...) """ struct StopWhenAll{TCriteria <: Tuple} <: StoppingCriterion criteria::TCriteria @@ -155,8 +155,8 @@ end function Base.show(io::IO, ::MIME"text/plain", stop_when_all::StopWhenAll) print(io, "StopWhenAll with the Stopping Criteria:") for stopping_criterion in stop_when_all.criteria - print(io, "\n ") - replace(io, sprint(show, stopping_criterion; context = io), "\n" => "\n ") #increase indent + print(io, "\n\t") + replace(io, sprint(show, stopping_criterion; context = io), "\n" => "\n\t") # increase indent end return nothing end @@ -174,7 +174,7 @@ appended to the list of [`StoppingCriterion`](@ref) within `s1` (or `s2`). Is the same as - a = StopWhenAll(StopAfterIteration(200), StopAfter(Minute(1)) + a = StopWhenAll(StopAfterIteration(200), StopAfter(Minute(1))) """ Base.:&(s1::StoppingCriterion, s2::StoppingCriterion) = StopWhenAll(s1, s2) Base.:&(s1::StoppingCriterion, s2::StopWhenAll) = StopWhenAll(s1, s2.criteria...) @@ -184,20 +184,20 @@ Base.:&(s1::StopWhenAll, s2::StopWhenAll) = StopWhenAll(s1.criteria..., s2.crite @doc raw""" StopWhenAny <: StoppingCriterion -store an array of [`StoppingCriterion`](@ref) elements and indicates to stop, +Store a tuple of [`StoppingCriterion`](@ref) elements and indicate to stop when _any_ single one indicates to stop. The `reason` is given by the -concatenation of all reasons (assuming that all non-indicating return `""`). +concatenation of all reasons (assuming that all non-indicating ones return `nothing`). # Constructors - StopWhenAny(c::Vector{N,StoppingCriterion} where N) + StopWhenAny(c::AbstractVector{<:StoppingCriterion}) StopWhenAny(c::StoppingCriterion...) """ struct StopWhenAny{TCriteria <: Tuple} <: StoppingCriterion criteria::TCriteria - StopWhenAny(c::Vector{<:StoppingCriterion}) = new{typeof(tuple(c...))}(tuple(c...)) StopWhenAny(c::StoppingCriterion...) = new{typeof(c)}(c) end +StopWhenAny(c::AbstractVector{<:StoppingCriterion}) = StopWhenAny(c...) function indicates_convergence(stop_when_any::StopWhenAny) return all(indicates_convergence, stop_when_any.criteria) @@ -206,8 +206,8 @@ end function Base.show(io::IO, ::MIME"text/plain", stop_when_any::StopWhenAny) print(io, "StopWhenAny with the Stopping Criteria:") for stopping_criterion in stop_when_any.criteria - print(io, "\n ") - replace(io, sprint(show, stopping_criterion; context = io), "\n" => "\n ") #increase indent + print(io, "\n\t") + replace(io, sprint(show, stopping_criterion; context = io), "\n" => "\n\t") # increase indent end return nothing end @@ -238,10 +238,10 @@ Base.:|(s1::StopWhenAny, s2::StopWhenAny) = StopWhenAny(s1.criteria..., s2.crite A [`StoppingCriterionState`](@ref) that groups multiple [`StoppingCriterionState`](@ref)s internally as a tuple. -This is for example used in combination with [`StopWhenAny`](@ref) and [`StopWhenAll`](@ref) +This is for example used in combination with [`StopWhenAny`](@ref) and [`StopWhenAll`](@ref). # Constructor - GroupStoppingCriterionState(c::Vector{N,StoppingCriterionState} where N) + GroupStoppingCriterionState(c::StoppingCriterionState...) """ mutable struct GroupStoppingCriterionState{TCriteriaStates <: Tuple} <: StoppingCriterionState @@ -356,8 +356,8 @@ function Base.summary( r = "Stop when _one_ of the following are fulfilled:\n" for (stopping_criterion, stopping_criterion_state) in zip(stop_when_any.criteria, stopping_criterion_states.criteria_states) - t = replace(summary(stopping_criterion, stopping_criterion_state), "\n" => "\n ") - r = "$(r) $(t)\n" + t = replace(summary(stopping_criterion, stopping_criterion_state), "\n" => "\n\t") + r = "$(r)\t$(t)\n" end return print(io, "$(r)Overall: $(s)") end @@ -370,8 +370,8 @@ function Base.summary( r = "Stop when _all_ of the following are fulfilled:\n" for (stopping_criterion, stopping_criterion_state) in zip(stop_when_all.criteria, stopping_criterion_states.criteria_states) - t = replace(summary(stopping_criterion, stopping_criterion_state), "\n" => "\n ") - r = "$(r) $(t)\n" + t = replace(summary(stopping_criterion, stopping_criterion_state), "\n" => "\n\t") + r = "$(r)\t$(t)\n" end return print(io, "$(r)Overall: $(s)") end @@ -387,29 +387,29 @@ A simple stopping criterion to stop after a maximal number of iterations. # Fields -* `max_iterations` stores the maximal iteration number where to stop at +* `max_iterations` stores the iteration number at which to stop. # Constructor - StopAfterIteration(maxIter) + StopAfterIteration(max_iterations) -initialize the functor to indicate to stop after `maxIter` iterations. +Initialize the criterion to indicate stopping after `max_iterations` iterations. """ struct StopAfterIteration <: StoppingCriterion max_iterations::Int end """ -DefaultStoppingCriterionState <: StoppingCriterionState + DefaultStoppingCriterionState <: StoppingCriterionState A [`StoppingCriterionState`](@ref) that does not require any information besides -storing the iteration number when it (last) indicated to stop). +storing the iteration number at which it (last) indicated to stop. + +# Fields -# Field -* `at_iteration::Int` store the iteration number this state - indicated to stop. - * `0` means already at the start it indicated to stop - * any negative number means that it did not yet indicate to stop. +* `at_iteration::Int` stores the iteration number at which this state indicated to stop. + * `0` means it already indicated to stop at the start. + * any negative number means that it has not yet indicated to stop. """ mutable struct DefaultStoppingCriterionState <: StoppingCriterionState at_iteration::Int @@ -464,25 +464,25 @@ function Base.summary( ) has_stopped = (stopping_criterion_state.at_iteration >= 0) s = has_stopped ? "reached" : "not reached" - return print(io, "Max Iteration $(stop_after_iteration.max_iterations): $s") + return print(io, "Max Iterations ($(stop_after_iteration.max_iterations)): $s") end """ StopAfter <: StoppingCriterion -store a threshold when to stop looking at the complete runtime. It uses -`time_ns()` to measure the time and you provide a `Period` as a time limit, +Stores a threshold for stopping based on the total runtime. It uses +`time_ns()` to measure the time, and you provide a `Period` as the time limit, for example `Minute(15)`. # Fields -* `threshold` stores the `Period` after which to stop +* `threshold` stores the `Period` after which to stop. # Constructor - StopAfter(t) + StopAfter(t::Period) -initialize the stopping criterion to a `Period t` to stop after. +Initialize the stopping criterion to stop after the `Period` `t` has elapsed. """ struct StopAfter <: StoppingCriterion threshold::Period @@ -502,11 +502,12 @@ end A state for stopping criteria that are based on time measurements, for example [`StopAfter`](@ref). -* `start` stores the starting time when the algorithm is started, that is a call with `i=0`. -* `time` stores the elapsed time -* `at_iteration` indicates at which iteration (including `i=0`) the stopping criterion - was fulfilled and is `-1` while it is not fulfilled. +# Fields +* `start` stores the starting time, recorded when the algorithm is started (the call with `k=0`). +* `time` stores the elapsed time. +* `at_iteration` indicates at which iteration (including `k=0`) the stopping criterion + was fulfilled, and is `-1` while it is not fulfilled. """ mutable struct StopAfterTimePeriodState <: StoppingCriterionState start::Nanosecond @@ -572,6 +573,6 @@ function Base.summary( ) has_stopped = (stopping_criterion_state.at_iteration >= 0) s = has_stopped ? "reached" : "not reached" - return print(io, "stopped after $(stop_after.threshold): $s") + return print(io, "stopped after $(stop_after.threshold): $s") end indicates_convergence(stop_after::StopAfter) = false diff --git a/src/test_suite.jl b/src/test_suite.jl index 4788c6d..9574c86 100644 --- a/src/test_suite.jl +++ b/src/test_suite.jl @@ -2,7 +2,7 @@ AlgorithmsInterface.Test The module `AlgorithmsInterface.Test` contains concrete (dummy) instances -to test prats of the interface. +to test parts of the interface. """ module Test using ..AlgorithmsInterface diff --git a/test/stopping_criterion.jl b/test/stopping_criterion.jl index b13e5b7..486d8a0 100644 --- a/test/stopping_criterion.jl +++ b/test/stopping_criterion.jl @@ -20,7 +20,7 @@ problem = AIT.DummyProblem() # Fake a stop: s1_state.at_iteration = 2 @test startswith(get_reason(s1, s1_state), "At iteration 2") - @test endswith(summary(s1, s1_state), " reached") + @test endswith(summary(s1, s1_state), ": reached") end @testset "StopAfter" begin @@ -42,7 +42,7 @@ end @test is_finished!(problem, algorithm, alg_state) @test is_finished(problem, algorithm, alg_state) @test startswith(get_reason(s1, s1_state), "After iteration 2") - @test endswith(summary(s1, s1_state), " reached") + @test endswith(summary(s1, s1_state), ": reached") end @testset "StopWhenAll" begin @@ -54,7 +54,7 @@ end @test s1 == s1b @test s1 isa StoppingCriterion @test sprint((io, x) -> show(io, MIME"text/plain"(), x), s1; context = :module => @__MODULE__) == - "StopWhenAll with the Stopping Criteria:\n StopAfterIteration(2)\n StopAfter(Nanosecond(2))" + "StopWhenAll with the Stopping Criteria:\n\tStopAfterIteration(2)\n\tStopAfter(Nanosecond(2))" algorithm = AIT.DummyAlgorithm(s1) s1_state = initialize_state(problem, algorithm, s1) @@ -98,7 +98,7 @@ end @test s1 isa StoppingCriterion @test s1 == StopWhenAny([c1, c2]) @test sprint((io, x) -> show(io, MIME"text/plain"(), x), s1; context = :module => @__MODULE__) == - "StopWhenAny with the Stopping Criteria:\n StopAfterIteration(2)\n StopAfter(Second(1))" + "StopWhenAny with the Stopping Criteria:\n\tStopAfterIteration(2)\n\tStopAfter(Second(1))" @test !indicates_convergence(s1) algorithm = AIT.DummyAlgorithm(s1)