conspire/math/optimize/krylov/
mod.rs1#[cfg(test)]
2mod test;
3
4use super::{OptimizationError, Precondition};
5use crate::math::{
6 Scalar, Style, StyledError, Tensor, Vector, assert::AssertionError, styled_error,
7};
8use std::mem::replace;
9
10const PATIENCE: usize = 30;
11const PROGRESS: Scalar = 0.9;
12const ACCEPTABLE: Scalar = 1e-3;
13
14#[derive(Clone, Copy, Debug, Default)]
16pub enum KrylovMethod {
17 #[default]
24 ConjugateGradients,
25 Minres,
32 Gmres(usize),
40}
41
42#[derive(Clone, Copy, Debug)]
44pub struct Krylov {
45 pub max_steps: usize,
47 pub method: KrylovMethod,
49 pub rel_tol: Scalar,
51}
52
53impl Default for Krylov {
54 fn default() -> Self {
55 Self {
56 max_steps: 1_000,
57 method: KrylovMethod::default(),
58 rel_tol: 1e-10,
59 }
60 }
61}
62
63impl Krylov {
64 pub fn solve(
66 &self,
67 apply: impl FnMut(&Vector) -> Vector,
68 preconditioning: impl Precondition,
69 right_hand_side: &Vector,
70 ) -> Result<Vector, KrylovError> {
71 match self.method {
72 KrylovMethod::ConjugateGradients => descend(
73 self.max_steps,
74 self.rel_tol,
75 apply,
76 preconditioning,
77 right_hand_side,
78 ),
79 KrylovMethod::Minres => minimize_residual(
80 self.max_steps,
81 self.rel_tol,
82 apply,
83 preconditioning,
84 right_hand_side,
85 ),
86 KrylovMethod::Gmres(restart) => restarted(
87 self.max_steps,
88 restart,
89 self.rel_tol,
90 apply,
91 preconditioning,
92 right_hand_side,
93 ),
94 }
95 }
96}
97
98fn descend(
99 max_steps: usize,
100 rel_tol: Scalar,
101 mut apply: impl FnMut(&Vector) -> Vector,
102 preconditioning: impl Precondition,
103 right_hand_side: &Vector,
104) -> Result<Vector, KrylovError> {
105 let scale = right_hand_side.norm().value();
106 let mut solution = Vector::zero(right_hand_side.len());
107 if scale == 0.0 {
108 return Ok(solution);
109 }
110 let divide = |residual: &Vector| preconditioning.apply(residual);
111 let mut residual = right_hand_side.clone();
112 let mut preconditioned = divide(&residual);
113 let mut direction = preconditioned.clone();
114 let mut projection = residual.full_contraction(&preconditioned);
115 let mut applied;
116 let mut curvature;
117 let mut next;
118 let mut step;
119 for _ in 0..max_steps {
120 applied = apply(&direction);
121 curvature = direction.full_contraction(&applied);
122 if curvature <= 0.0 {
123 return Err(KrylovError::NotPositiveDefinite(curvature));
124 }
125 step = projection / curvature;
126 solution += &direction * step;
127 residual -= applied * step;
128 if residual.norm().value() <= rel_tol * scale {
129 return Ok(solution);
130 }
131 preconditioned = divide(&residual);
132 next = residual.full_contraction(&preconditioned);
133 direction *= next / projection;
134 direction += &preconditioned;
135 projection = next
136 }
137 Err(KrylovError::MaximumStepsReached(
138 max_steps,
139 residual.norm().value() / scale,
140 ))
141}
142
143fn minimize_residual(
144 max_steps: usize,
145 rel_tol: Scalar,
146 mut apply: impl FnMut(&Vector) -> Vector,
147 preconditioning: impl Precondition,
148 right_hand_side: &Vector,
149) -> Result<Vector, KrylovError> {
150 let size = right_hand_side.len();
151 let mut solution = Vector::zero(size);
152 let divide = |residual: &Vector| preconditioning.apply(residual);
153 let mut previous = right_hand_side.clone();
154 let mut current = previous.clone();
155 let mut preconditioned = divide(&previous);
156 let squared = previous.full_contraction(&preconditioned);
157 if squared < 0.0 {
158 return Err(KrylovError::PreconditionerNotPositiveDefinite(squared));
159 }
160 let scale = squared.sqrt();
161 if scale == 0.0 {
162 return Ok(solution);
163 }
164 let (mut cosine, mut sine) = (-1.0, 0.0);
165 let mut length = scale;
166 let mut off_diagonal = scale;
167 let (mut previous_off, mut carried, mut trailing) = (0.0, 0.0, 0.0 as Scalar);
168 let mut direction = Vector::zero(size);
169 let mut older;
170 let mut old = Vector::zero(size);
171 let mut basis;
172 let load = right_hand_side.norm().value();
173 let mut watched = 1.0;
174 let mut demanded = rel_tol;
175 for step in 0..max_steps {
176 basis = &preconditioned * off_diagonal.recip();
177 preconditioned = apply(&basis);
178 if step > 0 {
179 preconditioned -= &previous * (off_diagonal / previous_off)
180 }
181 let diagonal_entry = basis.full_contraction(&preconditioned);
182 preconditioned -= ¤t * (diagonal_entry / off_diagonal);
183 previous = replace(&mut current, preconditioned);
184 preconditioned = divide(¤t);
185 previous_off = off_diagonal;
186 let squared = current.full_contraction(&preconditioned);
187 if squared < -Scalar::EPSILON * scale * scale {
188 return Err(KrylovError::PreconditionerNotPositiveDefinite(squared));
189 }
190 off_diagonal = squared.max(0.0).sqrt();
191 let reached = carried;
192 let shifted = cosine * trailing + sine * diagonal_entry;
193 let remaining = sine * trailing - cosine * diagonal_entry;
194 carried = sine * off_diagonal;
195 trailing = -cosine * off_diagonal;
196 let rotated = remaining
197 .hypot(off_diagonal)
198 .max(Scalar::EPSILON * scale.max(1.0));
199 cosine = remaining / rotated;
200 sine = off_diagonal / rotated;
201 older = replace(&mut old, direction);
202 direction = (basis - &older * reached - &old * shifted) * rotated.recip();
203 solution += &direction * (cosine * length);
204 length *= sine;
205 let estimate = length.abs() / scale;
206 let checkpoint = step % PATIENCE == PATIENCE - 1;
207 if estimate <= demanded || checkpoint {
208 let truth = (right_hand_side.clone() - apply(&solution)).norm().value() / load;
209 if truth <= rel_tol {
210 return Ok(solution);
211 }
212 if checkpoint {
213 if truth > PROGRESS * watched {
214 return if truth <= ACCEPTABLE {
215 Ok(solution)
216 } else {
217 Err(KrylovError::StoppedShortening(step + 1, truth))
218 };
219 }
220 watched = truth
221 }
222 demanded = demanded.min(estimate * 0.1)
223 }
224 }
225 Err(KrylovError::MaximumStepsReached(
226 max_steps,
227 (right_hand_side.clone() - apply(&solution)).norm().value() / load,
228 ))
229}
230
231fn restarted(
232 max_steps: usize,
233 restart: usize,
234 rel_tol: Scalar,
235 mut apply: impl FnMut(&Vector) -> Vector,
236 preconditioning: impl Precondition,
237 right_hand_side: &Vector,
238) -> Result<Vector, KrylovError> {
239 let scale = right_hand_side.norm().value();
240 let mut solution = Vector::zero(right_hand_side.len());
241 if scale == 0.0 {
242 return Ok(solution);
243 }
244 let restart = restart.max(1);
245 let mut steps = 0;
246 let mut relative = 1.0;
247 let mut residual = right_hand_side.clone();
248 while steps < max_steps {
249 let beta = residual.norm().value();
250 let mut basis = vec![&residual * beta.recip()];
251 let mut preconditioned = Vec::new();
252 let mut triangle = Vec::<Vec<Scalar>>::new();
253 let mut rotations = Vec::<(Scalar, Scalar)>::new();
254 let mut projected = vec![beta];
255 let mut inner = 0;
256 while inner < restart && steps < max_steps {
257 preconditioned.push(preconditioning.apply(&basis[inner]));
258 let mut next = apply(&preconditioned[inner]);
259 let mut column = vec![0.0; inner + 2];
260 for (entry, vector) in column.iter_mut().zip(&basis) {
261 *entry = next.full_contraction(vector);
262 next -= vector * *entry;
263 }
264 let subdiagonal = next.norm().value();
265 column[inner + 1] = subdiagonal;
266 for (row, &(cosine, sine)) in rotations.iter().enumerate() {
267 let upper = cosine * column[row] + sine * column[row + 1];
268 column[row + 1] = cosine * column[row + 1] - sine * column[row];
269 column[row] = upper;
270 }
271 let length = column[inner].hypot(column[inner + 1]);
272 let (cosine, sine) = if length == 0.0 {
273 (1.0, 0.0)
274 } else {
275 (column[inner] / length, column[inner + 1] / length)
276 };
277 column[inner] = length;
278 column.truncate(inner + 1);
279 triangle.push(column);
280 rotations.push((cosine, sine));
281 projected.push(-sine * projected[inner]);
282 projected[inner] *= cosine;
283 inner += 1;
284 steps += 1;
285 relative = projected[inner].abs() / scale;
286 if relative <= rel_tol || subdiagonal <= Scalar::EPSILON * beta {
287 break;
288 }
289 basis.push(next * subdiagonal.recip());
290 }
291 let mut coefficients = vec![0.0; inner];
292 for row in (0..inner).rev() {
293 let tail: Scalar = (row + 1..inner)
294 .map(|column| triangle[column][row] * coefficients[column])
295 .sum();
296 coefficients[row] = (projected[row] - tail) / triangle[row][row];
297 }
298 for (coefficient, vector) in coefficients.iter().zip(&preconditioned) {
299 solution += vector * *coefficient;
300 }
301 residual = right_hand_side.clone() - apply(&solution);
302 relative = residual.norm().value() / scale;
303 if relative <= rel_tol {
304 return Ok(solution);
305 }
306 }
307 Err(KrylovError::MaximumStepsReached(max_steps, relative))
308}
309
310pub enum KrylovError {
312 MaximumStepsReached(usize, Scalar),
313 NotPositiveDefinite(Scalar),
314 PreconditionerNotPositiveDefinite(Scalar),
315 StoppedShortening(usize, Scalar),
316}
317
318impl StyledError for KrylovError {
319 fn message(&self, style: &Style) -> String {
320 let (h, c) = (style.headline, style.frame);
321 match self {
322 Self::MaximumStepsReached(steps, relative) => format!(
323 "{h}Maximum number of iterations ({steps}) reached.{c}\n\
324 Residual relative to the one started from: {relative:?}."
325 ),
326 Self::NotPositiveDefinite(curvature) => format!(
327 "{h}The operator is not positive definite.{c}\n\
328 Curvature along a direction: {curvature:?}."
329 ),
330 Self::PreconditionerNotPositiveDefinite(squared) => format!(
331 "{h}The preconditioner is not positive definite.{c}\n\
332 Squared length of a residual through it: {squared:?}."
333 ),
334 Self::StoppedShortening(steps, relative) => format!(
335 "{h}The residual stopped shortening after {steps} iterations.{c}\n\
336 Residual relative to the one started from: {relative:?}."
337 ),
338 }
339 }
340}
341
342styled_error!(KrylovError);
343
344impl From<KrylovError> for OptimizationError {
345 fn from(error: KrylovError) -> Self {
346 Self::Upstream(error.to_string(), "Krylov".to_string())
347 }
348}
349
350impl From<KrylovError> for AssertionError {
351 fn from(error: KrylovError) -> Self {
352 Self {
353 message: error.to_string(),
354 }
355 }
356}