1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
use ;
/// Algorithms implement this trait to work with the library.
///
/// # Overview
/// The algo has a `Step` method, similar to iterator's `next`.
///
/// The "problem" being solved and any initial starting conditions are part of the algo, not
/// seperate entities.
///
/// The return parameter indicates whether iteration *can* continue, and propagates any errors encountered.
///
/// `ControlFlow::Continue(())` indicates that the algo could continue
/// though the final decision is made by the calling code (perhaps by
/// logic within [`Driver::converge_when`](crate::Driver::converge_when) ).
///
/// `ControlFlow::Break(())` indicates that the algo has converged or errored, and no more iteration steps are possible.
///
/// Independently, any errors occuring are returned in the tuple.
///
/// For many algorithms, each step depends on the previous step. In such cases,
/// the algorithm will return `Break` whenever an error occurs, as iteration cannot continue.
/// An algorithm such as a file line-parser, might continue iteration, collecting up all
/// errors in the file. In this case `(ControlFlow::Continue, Err(e))` would be used.
///
/// Whereas iterators return their current value in the call to next,
/// algorithms return `()` from step.
///
/// But to be useful, they will need accessors or public variables to access
/// the solution after after, or during iteration.
///
/// If called before the first call to `step`, they
/// could return the initial starting conditions of the algorithm.
///
/// By convention, the current best solution would be called `x()`
/// for a scalar or vector solution.
///
///
/// # Example 1:
/// Note that the hyperparameter (learning rate) and initial/best
/// solution are considered part of the algorithm. As is the problem being solved (in this case a gradient function).
///
/// The learning rate is public, and can be changed (is _adaptive_) during the optimization process.
/// ```
/// use std::{error::Error, ops::ControlFlow, convert::Infallible};
/// use stepwise::{Algo, BoxedError};
///
/// //
/// pub struct GradientDescent<G> {
/// pub gradient_fn: G,
/// // current best solution, seeded with initial guess
/// pub x: Vec<f64>,
/// pub learning_rate: f64,
/// }
///
/// impl<G> Algo for GradientDescent<G>
/// where
/// G: Fn(&[f64]) -> Vec<f64>
/// {
/// type Error = Infallible;
///
/// fn step(&mut self) -> (ControlFlow<()>, Result<(), Infallible>) {
/// let dfdx = (self.gradient_fn)(&self.x);
/// for i in 0..self.x.len() {
/// self.x[i] -= self.learning_rate * dfdx[i];
/// }
///
/// // Allow the calling client to decide when to cease iteration.
/// // If our algorithm managed convergence/iteration counts internally,
/// // `ControlFlow::Break` would be returned to terminate iteration.
/// (ControlFlow::Continue(()), Ok(()))
/// }
/// }
/// ```
///
/// # Example 2:
/// This is the bisection algorithm, used to find the root (solution) to `f(x) = 0`
///
/// ```
/// # use stepwise::prelude::*;
/// # use std::ops::ControlFlow;
/// # use std::error::Error;
/// pub struct Bisection<F> {
/// f: F, // the objective function being solved: Fn(f64) -> Result<f64>
/// x: [f64; 2], // lower and upper bounds around the solution
/// f_lo: f64,
/// f_mid: f64, // mid point of last function evaluations at lower and upper bounds
/// }
///
/// // There are no restrictions on non-trait access (or setter) methods, as these are
/// // defined outside of the trait.
/// impl<F> Bisection<F> {
/// // The lower and upper bounds around the the "best solution", so are designated `x`
/// pub fn x(&self) -> [f64; 2] {
/// self.x
/// }
///
/// // The midpoint of the current range.
/// pub fn x_mid(&self) -> f64 {
/// (self.x[0] + self.x[1]) / 2.0
/// }
///
/// // The function, `f`, evaluated at the midpoint of the current range.
/// pub fn f_mid(&self) -> f64 {
/// self.f_mid
/// }
/// }
///
///
/// impl<E, F> Algo for Bisection<F>
/// where
/// F: FnMut(f64) -> Result<f64, E>,
/// E: 'static + Send + Sync + Error,
/// {
/// type Error = E;
///
/// fn step(&mut self) -> (ControlFlow<()>, Result<(), E>) {
/// let mid = (self.x[0] + self.x[1]) / 2.0;
/// match (self.f)(mid) {
/// Ok(y) => self.f_mid = y,
/// // our objective function is falliable, so we propagate any errors
/// // we return `Break` if we encounter an error, as futher
/// // iteration is not possible
/// Err(e) => return (ControlFlow::Break(()), Err(e)),
/// };
///
/// if self.f_mid == 0.0 {
/// self.x = [mid, mid];
/// /// return `Break` if we know we have converged
/// return (ControlFlow::Break(()), Ok(()));
/// }
///
/// if self.f_mid * self.f_lo < 0.0 {
/// self.x[1] = mid;
/// (ControlFlow::Continue(()),Ok(()))
/// } else {
/// self.x[0] = mid;
/// self.f_lo = self.f_mid;
/// (ControlFlow::Continue(()),Ok(()))
/// }
/// }
/// }
/// ```
///
/// Used internally by algorithms to determine whether they can support writing a checkpoint file of the designated type.
///
/// When checkpoints are specified by [`Driver::checkpoint`](crate::Driver::checkpoint), the file format is determined from
/// the file extension of the supplied path.
///
pub
pub