From 4d41af307c03eb0d44a4699221c7c0cc53180b60 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 27 Apr 2026 08:46:18 -0400 Subject: [PATCH 1/6] allow customization of output --- src/AlgorithmsInterface.jl | 1 + src/interface/interface.jl | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/AlgorithmsInterface.jl b/src/AlgorithmsInterface.jl index 2a9ed68..54eb559 100644 --- a/src/AlgorithmsInterface.jl +++ b/src/AlgorithmsInterface.jl @@ -23,6 +23,7 @@ include("logging.jl") # general interface export Algorithm, Problem, State export initialize_state, initialize_state! +export finalize_state! export step!, solve, solve! diff --git a/src/interface/interface.jl b/src/interface/interface.jl index 8640795..657282d 100644 --- a/src/interface/interface.jl +++ b/src/interface/interface.jl @@ -17,6 +17,16 @@ function initialize_state! end @doc "$(_doc_init_state)" initialize_state!(::Problem, ::Algorithm, ::State; kwargs...) + +""" + output = finalize_state!(problem::Problem, algorithm::Algorithm, state::State) + +Finalize the solver and decide what values get returned from the [`solve!`](@ref) call. +By default, this is a no-op and returns the `state`, but this allows for customization +in cases where the details of the `state` can remain hidden. +""" +finalize_state!(problem::Problem, algorithm::Algorithm, state::State) = state + # has to be defined before used in solve but is documented alphabetically after @doc """ @@ -66,9 +76,10 @@ function solve!(problem::Problem, algorithm::Algorithm, state::State; kwargs...) end # emit message about finished state + output = finalize_state!(problem, algorithm, state) emit_message(logger, problem, algorithm, state, :Stop) - return state + return output end function step! end From b51b7e008d2fad2882bc25e39ffd7a3abbe61106 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 27 Apr 2026 08:48:17 -0400 Subject: [PATCH 2/6] avoid double `initialize_state` call in `solve` --- src/interface/interface.jl | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/interface/interface.jl b/src/interface/interface.jl index 657282d..1bf8e1b 100644 --- a/src/interface/interface.jl +++ b/src/interface/interface.jl @@ -40,8 +40,33 @@ returns a state. By default this method continues to call [`solve!`](@ref). """ function solve(problem::Problem, algorithm::Algorithm; kwargs...) + # obtain logger once to minimize overhead from accessing ScopedValue + # additionally handle logging initialization to enable stateful LoggingAction + logger = algorithm_logger() + # initialize_logger(logger, problem, algorithm, state) + + # initialize the state and emit message state = initialize_state(problem, algorithm; kwargs...) - return solve!(problem, algorithm, state; kwargs...) + emit_message(logger, problem, algorithm, state, :Start) + + # main body of the algorithm + while !is_finished!(problem, algorithm, state) + # logging event between convergence check and algorithm step + emit_message(logger, problem, algorithm, state, :PreStep) + + # algorithm step + increment!(state) + step!(problem, algorithm, state) + + # logging event between algorithm step and convergence check + emit_message(logger, problem, algorithm, state, :PostStep) + end + + # emit message about finished state + output = finalize_state!(problem, algorithm, state) + emit_message(logger, problem, algorithm, state, :Stop) + + return output end @doc """ From 7b8de8b6f64014e7bd694287d2162dfa242dfd81 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 27 Apr 2026 09:03:20 -0400 Subject: [PATCH 3/6] centralize `_solve_body!` code --- src/interface/interface.jl | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/interface/interface.jl b/src/interface/interface.jl index 1bf8e1b..c3e379c 100644 --- a/src/interface/interface.jl +++ b/src/interface/interface.jl @@ -49,24 +49,7 @@ function solve(problem::Problem, algorithm::Algorithm; kwargs...) state = initialize_state(problem, algorithm; kwargs...) emit_message(logger, problem, algorithm, state, :Start) - # main body of the algorithm - while !is_finished!(problem, algorithm, state) - # logging event between convergence check and algorithm step - emit_message(logger, problem, algorithm, state, :PreStep) - - # algorithm step - increment!(state) - step!(problem, algorithm, state) - - # logging event between algorithm step and convergence check - emit_message(logger, problem, algorithm, state, :PostStep) - end - - # emit message about finished state - output = finalize_state!(problem, algorithm, state) - emit_message(logger, problem, algorithm, state, :Stop) - - return output + return _solve_body!(problem, algorithm, state, logger) end @doc """ @@ -87,6 +70,10 @@ function solve!(problem::Problem, algorithm::Algorithm, state::State; kwargs...) initialize_state!(problem, algorithm, state; kwargs...) emit_message(logger, problem, algorithm, state, :Start) + return _solve_body!(problem, algorithm, state, logger) +end + +function _solve_body!(problem::Problem, algorithm::Algorithm, state::State, logger) # main body of the algorithm while !is_finished!(problem, algorithm, state) # logging event between convergence check and algorithm step From d18cde87a31aac2e71df5487c1f8e514a8c9df94 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 27 Apr 2026 10:05:09 -0400 Subject: [PATCH 4/6] refactor to use `solve_loop!` --- src/AlgorithmsInterface.jl | 2 +- src/interface/interface.jl | 47 ++++++++++++++++++++++++-------------- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/AlgorithmsInterface.jl b/src/AlgorithmsInterface.jl index 54eb559..0907dbd 100644 --- a/src/AlgorithmsInterface.jl +++ b/src/AlgorithmsInterface.jl @@ -25,7 +25,7 @@ export Algorithm, Problem, State export initialize_state, initialize_state! export finalize_state! -export step!, solve, solve! +export step!, solve, solve!, solve_loop! # stopping criteria export StoppingCriterion, StoppingCriterionState diff --git a/src/interface/interface.jl b/src/interface/interface.jl index c3e379c..fad19ea 100644 --- a/src/interface/interface.jl +++ b/src/interface/interface.jl @@ -43,13 +43,19 @@ function solve(problem::Problem, algorithm::Algorithm; kwargs...) # obtain logger once to minimize overhead from accessing ScopedValue # additionally handle logging initialization to enable stateful LoggingAction logger = algorithm_logger() - # initialize_logger(logger, problem, algorithm, state) # initialize the state and emit message state = initialize_state(problem, algorithm; kwargs...) emit_message(logger, problem, algorithm, state, :Start) - return _solve_body!(problem, algorithm, state, logger) + # main loop + state = solve_loop!(problem, algorithm, state) + + # emit message about finished state + output = finalize_state!(problem, algorithm, state) + emit_message(logger, problem, algorithm, state, :Stop) + + return output end @doc """ @@ -64,34 +70,41 @@ function solve!(problem::Problem, algorithm::Algorithm, state::State; kwargs...) # obtain logger once to minimize overhead from accessing ScopedValue # additionally handle logging initialization to enable stateful LoggingAction logger = algorithm_logger() - # initialize_logger(logger, problem, algorithm, state) # initialize the state and emit message initialize_state!(problem, algorithm, state; kwargs...) emit_message(logger, problem, algorithm, state, :Start) - return _solve_body!(problem, algorithm, state, logger) + # main loop + state = solve_loop!(problem, algorithm, state) + + # emit message about finished state + output = finalize_state!(problem, algorithm, state) + emit_message(logger, problem, algorithm, state, :Stop) + + return output end -function _solve_body!(problem::Problem, algorithm::Algorithm, state::State, logger) - # main body of the algorithm +""" + solve_loop!(problem::Problem, algorithm::Algorithm, state::State) + +Provide the main loop of the iterative `algorithm` for a given `problem` and starting `state`. + +This loop consists of: +1. Checking for convergence with [`is_finished!`](@ref) +2. Incrementing the state [`increment!`](@ref) +3. Performing a step [`step!`](@ref) +4. Repeat +""" +function solve_loop!(problem::Problem, algorithm::Algorithm, state::State) + logger = algorithm_logger() while !is_finished!(problem, algorithm, state) - # logging event between convergence check and algorithm step emit_message(logger, problem, algorithm, state, :PreStep) - - # algorithm step increment!(state) step!(problem, algorithm, state) - - # logging event between algorithm step and convergence check emit_message(logger, problem, algorithm, state, :PostStep) end - - # emit message about finished state - output = finalize_state!(problem, algorithm, state) - emit_message(logger, problem, algorithm, state, :Stop) - - return output + return state end function step! end From 06c792df26d36bc4f57cc0f6482e725d36cb8859 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 27 Apr 2026 10:05:46 -0400 Subject: [PATCH 5/6] swap `finalize` and `emit_message` order --- src/interface/interface.jl | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/interface/interface.jl b/src/interface/interface.jl index fad19ea..ff2e694 100644 --- a/src/interface/interface.jl +++ b/src/interface/interface.jl @@ -52,10 +52,9 @@ function solve(problem::Problem, algorithm::Algorithm; kwargs...) state = solve_loop!(problem, algorithm, state) # emit message about finished state - output = finalize_state!(problem, algorithm, state) emit_message(logger, problem, algorithm, state, :Stop) - return output + return finalize_state!(problem, algorithm, state) end @doc """ @@ -79,10 +78,9 @@ function solve!(problem::Problem, algorithm::Algorithm, state::State; kwargs...) state = solve_loop!(problem, algorithm, state) # emit message about finished state - output = finalize_state!(problem, algorithm, state) emit_message(logger, problem, algorithm, state, :Stop) - return output + return finalize_state!(problem, algorithm, state) end """ From 3d8a5dba84047177965d77f9f3b993357564ab50 Mon Sep 17 00:00:00 2001 From: lkdvos Date: Mon, 27 Apr 2026 11:44:33 -0400 Subject: [PATCH 6/6] default output `state.iterate` --- docs/src/interface.md | 5 +++-- docs/src/logging.md | 5 ++--- docs/src/stopping_criterion.md | 3 +-- src/interface/interface.jl | 6 +++--- test/newton.jl | 6 +++--- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/docs/src/interface.md b/docs/src/interface.md index 9d4817a..4ec3a27 100644 --- a/docs/src/interface.md +++ b/docs/src/interface.md @@ -128,13 +128,14 @@ With these definitions in place you can already run (assuming you also choose a function heron_sqrt(x; maxiter = 10) prob = SqrtProblem(x) alg = HeronAlgorithm(StopAfterIteration(maxiter)) - state = solve(prob, alg) # allocates & runs - return state.iterate + return solve(prob, alg) # allocates & runs end println("Approximate sqrt: ", heron_sqrt(16.0)) ``` +Note that [`solve`](@ref) will default to returning `state.iterate`. +If desired, this can be customized by altering [`finalize_state!`](@ref). We will refine this example with better halting logic and logging shortly. ## Reference: Core interface types & functions diff --git a/docs/src/logging.md b/docs/src/logging.md index c167abb..b2b5da1 100644 --- a/docs/src/logging.md +++ b/docs/src/logging.md @@ -75,8 +75,7 @@ end function heron_sqrt(x; stopping_criterion = StopAfterIteration(10)) prob = SqrtProblem(x) alg = HeronAlgorithm(stopping_criterion) - state = solve(prob, alg) # allocates & runs - return state.iterate + return solve(prob, alg) # allocates & runs end nothing # hide ``` @@ -329,7 +328,7 @@ function solve!(problem::Problem, algorithm::Algorithm, state::State; kwargs...) emit_message(problem, algorithm, state, :Stop) - return state + return finalize_state!(problem, algorithm, state) end ``` diff --git a/docs/src/stopping_criterion.md b/docs/src/stopping_criterion.md index f03c3e4..a7e2218 100644 --- a/docs/src/stopping_criterion.md +++ b/docs/src/stopping_criterion.md @@ -112,8 +112,7 @@ We can again combine everything into a single function, but now make the stoppin function heron_sqrt(x; stopping_criterion) prob = SqrtProblem(x) alg = HeronAlgorithm(stopping_criterion) - state = solve(prob, alg) # allocates & runs - return state.iterate, state.iteration + return solve(prob, alg) # allocates & runs end heron_sqrt(2; stopping_criterion = StopAfterIteration(10)) diff --git a/src/interface/interface.jl b/src/interface/interface.jl index ff2e694..f294dce 100644 --- a/src/interface/interface.jl +++ b/src/interface/interface.jl @@ -22,10 +22,10 @@ initialize_state!(::Problem, ::Algorithm, ::State; kwargs...) output = finalize_state!(problem::Problem, algorithm::Algorithm, state::State) Finalize the solver and decide what values get returned from the [`solve!`](@ref) call. -By default, this is a no-op and returns the `state`, but this allows for customization -in cases where the details of the `state` can remain hidden. +By default, this is a no-op and returns the `state.iterate`, but this allows for further +customization in other cases, for example to clean up used resources or output other data. """ -finalize_state!(problem::Problem, algorithm::Algorithm, state::State) = state +finalize_state!(problem::Problem, algorithm::Algorithm, state::State) = state.iterate # has to be defined before used in solve but is documented alphabetically after diff --git a/test/newton.jl b/test/newton.jl index 3a038aa..a60cb90 100644 --- a/test/newton.jl +++ b/test/newton.jl @@ -60,9 +60,9 @@ end problem = RootFindingProblem(x -> f(x, a), x -> df(x, a)) algorithm1 = NewtonMethod(StopAfterIteration(8)) solution1 = solve(problem, algorithm1) - @test solution1.iterate ≈ sqrt(a) + @test solution1 ≈ sqrt(a) algorithm2 = NewtonMethod(StopAfterIteration(10)) solution2 = solve(problem, algorithm2) - @test solution2.iterate ≈ sqrt(a) - @test abs(solution2.iterate - sqrt(a)) < abs(solution1.iterate - sqrt(a)) + @test solution2 ≈ sqrt(a) + @test abs(solution2 - sqrt(a)) < abs(solution1 - sqrt(a)) end