Skip to content

Commit d285b04

Browse files
committed
clean up and finish logging docs
1 parent 464882c commit d285b04

6 files changed

Lines changed: 59 additions & 33 deletions

File tree

docs/make.jl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,6 @@ makedocs(;
5252
],
5353
expandfirst = ["interface.md", "stopping_criterion.md"],
5454
plugins = [bib, links],
55-
warnonly = true
56-
5755
)
5856
deploydocs(; repo = "github.com/JuliaManifolds/AlgorithmsInterface.jl", push_preview = true)
5957
#back to main env

docs/src/interface.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ end
107107
```
108108

109109
Note that we are only focussing on the actual algorithm, and *not* incrementing the iteration counter.
110-
These kinds of bookkeeping should be handled by the [`increment!(state)`](@ref) function, which will by default already increment the iteration counter.
110+
These kinds of bookkeeping should be handled by the [`AlgorithmsInterface.increment!`](@ref) function, which will by default already increment the iteration counter.
111111
The following generic functionality is therefore enough for our purposes, and does *not* need to be defined.
112112
Nevertheless, if additional bookkeeping would be desired, this can be achieved by overloading that function:
113113

docs/src/logging.md

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ The logging system aims to achieve these goals by separating the logging logic i
2424
These parts can be roughly described as *events* and *actions*, where the logging system is responsible for mapping between them.
2525
Concretely, we have:
2626

27-
* **When do we log?** → an [`AlgorithmLogger`](@ref) mapping events to actions.
28-
* **What happens when we log?** → a [`LoggingAction`](@ref).
27+
* **When do we log?** → an [`with_algorithmlogger`](@ref) to control how to map events to actions.
28+
* **What happens when we log?** → a [`LoggingAction`](@ref) to determine what to do when an event happens.
2929

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

3232
## Using the default logging actions
3333

34-
Continuing from the [Stopping Criteria](@ref) page, we have our Heron's method implementation ready:
34+
Continuing from the [Stopping Criteria](@ref sec_stopping) page, we have our Heron's method implementation ready:
3535

3636
```@example Heron
3737
using AlgorithmsInterface
@@ -97,7 +97,7 @@ nothing # hide
9797
```
9898

9999
To activate this logger, we wrap the section of code that we want to enable logging for, and map the `:PostStep` context to our action.
100-
This is achieved through the [`with_algorithmlogger`](@ref) function, and uses Julia's `with` function to set the [`ALGORITHM_LOGGER`](@ref) scoped value:
100+
This is achieved through the [`with_algorithmlogger`](@ref) function, which under the hood uses Julia's `with` function to manipulate a scoped value.
101101

102102
```@example Heron
103103
with_algorithmlogger(:PostStep => iter_printer) do
@@ -246,7 +246,7 @@ Let's implement a more sophisticated example: tracking iteration statistics.
246246
To implement a custom [`LoggingAction`](@ref), you need:
247247

248248
1. A concrete subtype of `LoggingAction`.
249-
2. An implementation of [`handle_message!`](@ref) that defines the behavior.
249+
2. An implementation of [`AlgorithmsInterface.handle_message!`](@ref) that defines the behavior.
250250

251251
The signature of `handle_message!` is:
252252

@@ -306,7 +306,7 @@ This pattern of collecting data during iteration and post-processing afterward i
306306

307307
## [The AlgorithmLogger](@id sec_algorithmlogger)
308308

309-
The [`AlgorithmLogger`](@ref) is the dispatcher that routes logging events to actions.
309+
The [`AlgorithmsInterface.AlgorithmLogger`](@ref) is the dispatcher that routes logging events to actions.
310310
Understanding its design helps when adding custom logging contexts.
311311

312312
### How logging events are emitted
@@ -315,21 +315,19 @@ Inside the `solve!` function, logging events are emitted at key points:
315315

316316
```julia
317317
function solve!(problem::Problem, algorithm::Algorithm, state::State; kwargs...)
318-
logger = algorithm_logger()
319-
320318
initialize_state!(problem, algorithm, state; kwargs...)
321-
emit_message(logger, problem, algorithm, state, :Start)
319+
emit_message(problem, algorithm, state, :Start)
322320

323321
while !is_finished!(problem, algorithm, state)
324-
emit_message(logger, problem, algorithm, state, :PreStep)
322+
emit_message(problem, algorithm, state, :PreStep)
325323

326324
increment!(state)
327325
step!(problem, algorithm, state)
328326

329-
emit_message(logger, problem, algorithm, state, :PostStep)
327+
emit_message(problem, algorithm, state, :PostStep)
330328
end
331329

332-
emit_message(logger, problem, algorithm, state, :Stop)
330+
emit_message(problem, algorithm, state, :Stop)
333331

334332
return state
335333
end
@@ -364,7 +362,14 @@ AlgorithmsInterface.set_global_logging_state!(previous_state)
364362
nothing # hide
365363
```
366364

367-
When logging is disabled globally, [`algorithm_logger`](@ref) returns `nothing`, and `emit_message` becomes a no-op with minimal overhead.
365+
This works since the default implementation of [`emit_message`](@ref) first retrieves the current logger through [`AlgorithmsInterface.algorithm_logger`](@ref):
366+
367+
```julia
368+
emit_message(problem, algorithm, state, context; kwargs...) =
369+
emit_message(algorithm_logger(), problem, algorithm, state, context; kwargs...)
370+
```
371+
372+
When logging is disabled globally, [`algorithm_logger`](@ref AlgorithmsInterface.algorithm_logger) returns `nothing`, and `emit_message` becomes a no-op with minimal overhead.
368373

369374
### Error isolation
370375

@@ -493,7 +498,7 @@ Private = true
493498
You have now seen the three pillars of the AlgorithmsInterface:
494499

495500
* [**Interface**](@ref sec_interface): Defining algorithms with `Problem`, `Algorithm`, and `State`.
496-
* [**Stopping criteria**](@ref): Controlling when iteration halts with composable conditions.
501+
* [**Stopping criteria**](@ref sec_stopping): Controlling when iteration halts with composable conditions.
497502
* [**Logging**](@ref sec_logging): Instrumenting execution with flexible, composable actions.
498503

499504
Together, these patterns encourage modular, testable, and maintainable iterative algorithm design.

docs/src/stopping_criterion.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
CollapsedDocStrings = true
33
```
44

5-
# Stopping criteria
5+
# [Stopping criteria](@id sec_stopping)
66

77
Continuing the square‑root story from the [Interface](@ref sec_interface) page, we now decide **when** the iteration should halt.
88
A stopping criterion encapsulates halting logic separately from the algorithm update rule.

src/AlgorithmsInterface.jl

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,20 @@ include("interface/interface.jl")
2020
include("stopping_criterion.jl")
2121
include("logging.jl")
2222

23+
# general interface
2324
export Algorithm, Problem, State
25+
export initialize_state, initialize_state!
26+
27+
export step!, solve, solve!
28+
29+
# stopping criteria
2430
export StoppingCriterion, StoppingCriterionState
2531
export StopAfter, StopAfterIteration, StopWhenAll, StopWhenAny
26-
export AlgorithmLogger, with_algorithmlogger, emit_message
32+
33+
export is_finished, is_finished!, get_reason, indicates_convergence
34+
35+
# Logging interface
2736
export LoggingAction, CallbackAction, IfAction, GroupAction
28-
export is_finished, is_finished!
29-
export initialize_state, initialize_state!
30-
export step!, solve, solve!
37+
export with_algorithmlogger, emit_message
3138

3239
end # module AlgorithmsInterface

src/logging.jl

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,27 @@ end
8787
# Algorithm Logger
8888
# ----------------
8989
"""
90-
AlgorithmLogger(context => action, ...)
90+
AlgorithmLogger(context => action, ...) -> logger
9191
9292
Logging transformer that handles the logic of dispatching logging events to logging actions.
93+
This is implemented through `logger[context]`.
94+
95+
See also the scoped value [`AlgorithmsInterface.algorithm_logger`](@ref).
96+
"""
97+
struct AlgorithmLogger
98+
actions::Dict{Symbol, LoggingAction}
99+
end
100+
AlgorithmLogger(args::Pair...) = AlgorithmLogger(Dict{Symbol, LoggingAction}(args...))
101+
102+
Base.getindex(logger::AlgorithmLogger, context::Symbol) = get(logger.actions, context, nothing)
103+
104+
"""
105+
with_algorithmlogger(f, (context => action)::Pair{Symbol, LoggingAction}...)
106+
with_algorithmlogger((context => action)::Pair{Symbol, LoggingAction}...) do
107+
# insert arbitrary code here
108+
end
109+
110+
Run the given zero-argument function `f()` while mapping events of given `context`s to their respective `action`s.
93111
By default, the following events trigger a logging action with the given `context`:
94112
95113
| context | event |
@@ -99,16 +117,11 @@ By default, the following events trigger a logging action with the given `contex
99117
| :PostStep | The solver has taken a step. |
100118
| :Stop | The solver has finished. |
101119
102-
Specific algorithms can associate other events with other contexts.
120+
However, further events and actions can be emitted through the [`emit_message`](@ref) interface.
103121
104122
See also the scoped value [`AlgorithmsInterface.algorithm_logger`](@ref).
105123
"""
106-
struct AlgorithmLogger
107-
actions::Dict{Symbol, LoggingAction}
108-
end
109-
AlgorithmLogger(args::Pair...) = AlgorithmLogger(Dict{Symbol, LoggingAction}(args...))
110-
111-
function with_algorithmlogger(f, args...)
124+
@inline function with_algorithmlogger(f, args...)
112125
logger = AlgorithmLogger(args...)
113126
return with(f, ALGORITHM_LOGGER => logger)
114127
end
@@ -153,18 +166,21 @@ end
153166
emit_message(algorithm_logger, problem::Problem, algorithm::Algorithm, state::State, context::Symbol; kwargs...) -> nothing
154167
155168
Use the current or the provided algorithm logger to handle the logging event of the given `context`.
156-
The [`AlgorithmLogger`](@ref) is responsible for dispatching the correct events to the correct [`LoggingAction`](@ref)s.
169+
The first signature should be favored as it correctly handles accessing the `logger` and respecting global toggles for enabling and disabling the logging system.
170+
171+
The second signature should be used exclusively in (very) hot loops, where the overhead of [`AlgorithmsInterface.algorithm_logger()`](@ref) is too large.
172+
In this case, you can manually extract the `algorithm_logger()` once outside of the hot loop.
157173
"""
158174
emit_message(problem::Problem, algorithm::Algorithm, state::State, context::Symbol; kwargs...) =
159175
emit_message(algorithm_logger(), problem, algorithm, state, context; kwargs...)
160176
emit_message(::Nothing, problem::Problem, algorithm::Algorithm, state::State, context::Symbol; kwargs...) =
161177
nothing
162178
function emit_message(
163-
alglogger::AlgorithmLogger, problem::Problem, algorithm::Algorithm, state::State, context::Symbol;
179+
logger::AlgorithmLogger, problem::Problem, algorithm::Algorithm, state::State, context::Symbol;
164180
kwargs...
165181
)
166182
@noinline; @nospecialize
167-
action::LoggingAction = @something(get(alglogger.actions, context, nothing), return nothing)
183+
action::LoggingAction = @something(logger[context], return nothing)
168184

169185
# Try-catch around logging to avoid stopping the algorithm when a logging action fails
170186
# but still emit an error message

0 commit comments

Comments
 (0)