You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
@@ -52,31 +52,31 @@ Here, we delve a bit deeper into the core components of what made our algorithm
52
52
### Initialization
53
53
54
54
The first core component to enable working with stopping criteria is to extend the initialization step to include initializing a [`StoppingCriterionState`](@ref) as well.
55
-
This can conveniently be done through the same initialization functions we used for initializing the state:
56
-
57
-
-[`initialize_state`](@ref) constructs an entirely new stopping state for the algorithm
58
-
-[`initialize_state!`](@ref) (in-place) reset of an existing stopping state.
55
+
Since some of these may require _stateful_ implementations, we also keep a `stopping_criterion_state` that captures this, and thus needs to be initialized.
56
+
By default, the initialization happens automatically and the only thing that is left for us to do is to attach this `stopping_criterion_state` to the `state` in the [`initialize_state`](@ref) function, as we already saw before:
59
57
60
58
```@example Heron
61
-
function AlgorithmsInterface.initialize_state(problem::SqrtProblem, algorithm::HeronAlgorithm; kwargs...)
Note that we do not need to handle any stopping criteria in the [`initialize_state!`](@ref) function, as a separate call to [`AlgorithmsInterface.initialize_stopping_state!`](@ref) is made independently.
79
+
80
80
### Iteration
81
81
82
82
During the iteration procedure, as set out by our design principles, we do not have to modify any of the code, and the stopping criteria do not show up:
It is of course possible that we are not satisfied by the stopping criteria that are provided by default.
148
-
Suppose we want to stop when successive iterates change by less than `ϵ`, we could achieve this by implementing our own stopping criterion.
149
-
In order to do so, we need to define our own structs and implement the required interface.
150
-
Again, we split up the data into a _static_ part, the [`StoppingCriterion`](@ref), and a _dynamic_ part, the [`StoppingCriterionState`](@ref).
148
+
For example, we might check for convergence by squaring our current `iterate` and seeing if it equals the input value.
149
+
In order to do so, we need to define our own struct and implement the required interface.
150
+
151
+
```@example Heron
152
+
struct StopWhenSquared <: StoppingCriterion
153
+
tol::Float64 # when do we consider things to be converged
154
+
end
155
+
```
156
+
157
+
### Checking for convergence
158
+
159
+
Then, we need to implement the logic that checks whether an algorithm has finished, which is achieved through [`is_finished`](@ref) and [`is_finished!`](@ref).
160
+
161
+
```@example Heron
162
+
using AlgorithmsInterface: DefaultStoppingCriterionState
Note that we automatically obtain a `DefaultStoppingCriterionState` as the final argument, in which we have to store the iteration at which convergence is reached.
173
+
As this is a mutating operation that alters the `stopping_criterion_state`, we ensure that it is called exactly once per iteration, while the non-mutating version is simply used to inspect the current status.
Finally, we need to implement [`get_reason`](@ref) and [`indicates_convergence`](@ref).
192
+
These helper functions are required to interact with the [logging system](@ref sec_logging), to distinguish between states that are considered ongoing, stopped and converged, or stopped without convergence.
193
+
194
+
```@example Heron
195
+
function AlgorithmsInterface.get_reason(stopping_criterion::StopWhenSquared, stopping_criterion_state::DefaultStoppingCriterionState)
Then we are finally ready to test out our new stopping criteria.
206
+
207
+
```@example Heron
208
+
criterion = StopWhenSquared(1e-8)
209
+
heron_sqrt(16.0; stopping_criterion = criterion)
210
+
```
211
+
212
+
### Initialization
213
+
214
+
Now suppose we want to stop when successive iterates change by less than `ϵ`.
215
+
This can be achieved by introducing a new stopping criterion again, but now we have to retain the previous `iterate` in order to have something to compare against.
216
+
Similar to the algorithm `State`, we split up the data into a _static_ part, the [`StoppingCriterion`](@ref), and a _dynamic_ part, the [`StoppingCriterionState`](@ref).
Note that our mutable state holds both the `previous_iterate`, which we need to compare to,
165
-
as well as the iteration at which the condition was satisfied.
230
+
Note that our mutable state holds both the `previous_iterate`, which we need to compare to, as well as the iteration at which the condition was satisfied.
166
231
This is not strictly necessary, but can be convenient to have a persistent indication that convergence was reached.
167
232
168
-
### Initialization
169
-
170
233
In order to support these _stateful_ criteria, again an initialization phase is needed.
function AlgorithmsInterface.initialize_stopping_state!(
251
+
::Problem, ::Algorithm, ::State,
252
+
stopping_criterion::StopWhenStable,
253
+
stopping_criterion_state::StopWhenStableState;
180
254
kwargs...
181
-
)
182
-
st.previous_iterate = NaN
183
-
st.at_iteration = -1
184
-
st.delta = NaN
185
-
return st
255
+
)
256
+
stopping_criterion_state.previous_iterate = NaN
257
+
stopping_criterion_state.at_iteration = -1
258
+
stopping_criterion_state.delta = NaN
259
+
return stopping_criterion_state
186
260
end
187
261
```
188
262
189
-
### Checking for convergence
263
+
!!! note
190
264
191
-
Then, we need to implement the logic that checks whether an algorithm has finished, which is achieved through [`is_finished`](@ref) and [`is_finished!`](@ref).
192
-
Here, the mutating version alters the `stopping_criterion_state`, and should therefore be called exactly once per iteration, while the non-mutating version is simply used to inspect the current status.
265
+
While for this simple case this does not matter, note that there is a subtle detail associated to the initialization order of the `State` and `StoppingCriterionState` respectively.
266
+
For the first initialization, [`AlgorithmsInterface.initialize_stopping_state`](@ref) is called _before_ [`initialize_state`](@ref).
267
+
This is required since the `State` encapsulates the `StoppingCriterionState`.
268
+
On the other hand, during the solver, the [`AlgorithmsInterface.initialize_stopping_state!`](@ref) is called _before_ [`initialize_state`](@ref).
269
+
This can be important for example to ensure that the initialization time of the state is taken into account for the stopping criteria.
270
+
271
+
The remainder of the implementation follows straightforwardly, where we again take care to only mutate the `stopping_criterion_state` in the mutating `is_finished!` implementation.
Finally, we need to implement [`get_reason`](@ref) and [`indicates_convergence`](@ref).
228
-
These helper functions are required to interact with the [logging system](@ref sec_logging), to distinguish between states that are considered ongoing, stopped and converged, or stopped without convergence.
229
-
230
-
```@example Heron
231
304
function AlgorithmsInterface.get_reason(c::StopWhenStable, st::StopWhenStableState)
0 commit comments