1use nalgebra::{DMatrix, DVector};
17use std::collections::VecDeque;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23pub enum CosineCheckMode {
24 None,
26 Zero,
28 #[default]
30 Standard,
31 Variable,
33 Strict,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
41pub enum CoeffCheckMode {
42 None,
44 #[default]
46 Regular,
47 ForceRecent,
49 Combined,
51 RegularNoCosine,
53}
54
55#[derive(Debug, Clone)]
57pub enum GdiisError {
58 SingularMatrix,
60 CoefficientCheckFailed {
62 reason: String,
64 },
65 CosineCheckFailed {
67 cos: f64,
69 limit: f64,
71 },
72 RedundantVectors,
74 RatioTooLarge,
76 InsufficientHistory,
78}
79
80
81#[derive(Debug, Clone)]
85pub struct TriangularMatrix {
86 data: Vec<f64>,
88 size: usize,
90}
91
92impl TriangularMatrix {
93 pub fn new(size: usize) -> Self {
95 let len = (size * (size + 1)) / 2;
96 Self {
97 data: vec![0.0; len],
98 size,
99 }
100 }
101
102 pub fn size(&self) -> usize {
104 self.size
105 }
106
107 #[inline]
109 fn index(&self, i: usize, j: usize) -> usize {
110 let (row, col) = if i >= j { (i, j) } else { (j, i) };
111 (row * (row + 1)) / 2 + col
112 }
113
114 pub fn get(&self, i: usize, j: usize) -> f64 {
116 self.data[self.index(i, j)]
117 }
118
119 pub fn set(&mut self, i: usize, j: usize, value: f64) {
121 let idx = self.index(i, j);
122 self.data[idx] = value;
123 }
124
125 pub fn multiply_vector(&self, x: &[f64]) -> Vec<f64> {
131 let n = self.size;
132 let len = n.min(x.len());
134 let mut y = vec![0.0; len];
135
136 for i in 0..len {
137 for j in 0..len {
138 y[i] += self.get(i, j) * x[j];
139 }
140 }
141 y
142 }
143
144 pub fn scale(&mut self, factor: f64) {
146 for v in &mut self.data {
147 *v *= factor;
148 }
149 }
150
151 pub fn copy_from(&mut self, other: &TriangularMatrix) {
153 self.data.copy_from_slice(&other.data);
154 }
155
156 pub fn extend(&mut self) {
158 self.size += 1;
159 let new_len = (self.size * (self.size + 1)) / 2;
160 self.data.resize(new_len, 0.0);
161 }
162}
163
164pub struct DiisCoeffSolver {
169 convergence: f64,
171 max_coeff_sum: f64,
173}
174
175impl Default for DiisCoeffSolver {
176 fn default() -> Self {
177 Self {
178 convergence: 1e-8,
179 max_coeff_sum: 1e8,
180 }
181 }
182}
183
184impl DiisCoeffSolver {
185 pub fn new(convergence: f64) -> Self {
187 Self {
188 convergence,
189 max_coeff_sum: 1e8,
190 }
191 }
192
193 pub fn solve(
207 &self,
208 a: &TriangularMatrix,
209 a_inv: &mut TriangularMatrix,
210 coeffs: &mut [f64],
211 n_start: usize,
212 n_total: usize,
213 ) -> Result<usize, GdiisError> {
214 let mut n_used = n_start;
215
216 for n in (n_start + 1)..=n_total {
217 let coeffs_save: Vec<f64> = coeffs[..n].to_vec();
219
220 let ac = a.multiply_vector(&coeffs[..n]);
222 let mut y: Vec<f64> = vec![1.0; n];
223 for i in 0..n {
224 y[i] -= ac[i];
225 }
226
227 let err_max = y.iter().map(|v| v.abs()).fold(0.0_f64, f64::max);
228 let _y_len = (y.iter().map(|v| v * v).sum::<f64>() / n as f64).sqrt();
229
230 let mut iter = 0;
231 let max_iter = 10 * n;
232
233 while err_max > self.convergence && iter <= max_iter {
234 let dx = a_inv.multiply_vector(&y);
236
237 let dy = a.multiply_vector(&dx);
239
240 self.sr1_update(a_inv, &dy, &dx, n)?;
242
243 let s11: f64 = dy.iter().map(|v| v * v).sum();
245 let s22: f64 = y.iter().map(|v| v * v).sum();
246 let s12: f64 = dy.iter().zip(y.iter()).map(|(a, b)| a * b).sum();
247
248 let c12 = if s11 * s22 > 1e-30 {
249 s12 / (s11 * s22).sqrt()
250 } else {
251 0.0
252 };
253
254 let ss = if c12.abs() > 1e-3 && s11 > 1e-30 {
255 c12 * (s22 / s11).sqrt()
256 } else {
257 let dy_new = a.multiply_vector(&y);
259 let s11_new: f64 = dy_new.iter().map(|v| v * v).sum();
260 let s12_new: f64 = dy_new.iter().zip(y.iter()).map(|(a, b)| a * b).sum();
261 if s11_new > 1e-30 {
262 s12_new / s11_new * (s22 / s11_new).sqrt()
263 } else {
264 1.0
265 }
266 };
267
268 for i in 0..n {
270 coeffs[i] += ss * dx[i];
271 }
272
273 let coeff_sum: f64 = coeffs[..n].iter().sum();
275 if coeff_sum.abs() > self.max_coeff_sum {
276 coeffs[..n].copy_from_slice(&coeffs_save);
278 return Ok(n.saturating_sub(1).max(n_start));
279 }
280
281 let dx_len = (dx.iter().map(|v| v * v).sum::<f64>() / n as f64).sqrt();
283 if dx_len <= self.convergence * self.convergence {
284 break;
285 }
286
287 let ac = a.multiply_vector(&coeffs[..n]);
289 for i in 0..n {
290 y[i] = 1.0 - ac[i];
291 }
292
293 iter += 1;
294 }
295
296 if (iter == 0 || iter > max_iter) && err_max > self.convergence {
298 coeffs[..n].copy_from_slice(&coeffs_save);
299 return Ok(n.saturating_sub(1).max(n_start));
300 }
301
302 n_used = n;
303 }
304
305 let coeff_sum: f64 = coeffs[..n_used].iter().sum();
309 if coeff_sum.abs() <= 1e-4 {
310 for c in &mut coeffs[..n_used] {
312 *c = 0.0;
313 }
314 coeffs[0] = 1.0;
315 } else {
316 let scale = 1.0 / coeff_sum.abs();
318 for c in &mut coeffs[..n_used] {
319 *c *= scale;
320 }
321 }
322
323 Ok(n_used)
324 }
325
326 fn sr1_update(
328 &self,
329 a_inv: &mut TriangularMatrix,
330 dy: &[f64],
331 dx: &[f64],
332 n: usize,
333 ) -> Result<(), GdiisError> {
334 let a_inv_dy = a_inv.multiply_vector(dy);
336 let mut diff = vec![0.0; n];
337 for i in 0..n {
338 diff[i] = dx[i] - a_inv_dy[i];
339 }
340
341 let denom: f64 = dy.iter().zip(diff.iter()).map(|(a, b)| a * b).sum();
343
344 if denom.abs() < 1e-14 {
345 return Ok(()); }
347
348 for i in 0..n {
350 for j in 0..=i {
351 let update = diff[i] * diff[j] / denom;
352 let current = a_inv.get(i, j);
353 a_inv.set(i, j, current + update);
354 }
355 }
356
357 Ok(())
358 }
359}
360
361pub struct GdiisOptimizer {
365 pub max_vectors: usize,
367 pub cosine_check: CosineCheckMode,
369 pub coeff_check: CoeffCheckMode,
371 a_matrix: TriangularMatrix,
373 a_inv: TriangularMatrix,
375 scale: f64,
377 el2_min: f64,
379 solver: DiisCoeffSolver,
381}
382
383impl GdiisOptimizer {
384 pub fn new(max_vectors: usize) -> Self {
386 Self {
387 max_vectors,
388 cosine_check: CosineCheckMode::Standard,
389 coeff_check: CoeffCheckMode::Regular,
390 a_matrix: TriangularMatrix::new(0),
391 a_inv: TriangularMatrix::new(0),
392 scale: 1.0,
393 el2_min: 1.0,
394 solver: DiisCoeffSolver::default(),
395 }
396 }
397
398 pub fn reset(&mut self) {
400 self.a_matrix = TriangularMatrix::new(0);
401 self.a_inv = TriangularMatrix::new(0);
402 self.scale = 1.0;
403 self.el2_min = 1.0;
404 }
405
406 pub fn compute_step(
418 &mut self,
419 coords: &VecDeque<DVector<f64>>,
420 errors: &VecDeque<DVector<f64>>,
421 _hessians: &VecDeque<DMatrix<f64>>,
422 ) -> Result<(DVector<f64>, Vec<f64>, usize), GdiisError> {
423 let n = coords.len();
424 if n < 2 {
425 return Err(GdiisError::InsufficientHistory);
426 }
427
428 let n_use = n.min(self.max_vectors);
429 let dim = coords[0].len();
430
431 let current_size = self.a_matrix.size();
434 if current_size == 0 || current_size > n_use || n_use > current_size + 1 {
435 self.initialize_matrices(errors, n_use)?;
437 } else if n_use > current_size {
438 self.extend_matrices(errors, n_use)?;
439 }
440 let mut coeffs = vec![0.0; n_use];
444 coeffs[0] = 1.0;
445
446 let n_used = self.solver.solve(
447 &self.a_matrix,
448 &mut self.a_inv,
449 &mut coeffs,
450 1,
451 n_use,
452 )?;
453
454 self.validate_coefficients(&coeffs, n_used)?;
456
457 let mut x_interp = DVector::zeros(dim);
459 for (i, coord) in coords.iter().take(n_used).enumerate() {
460 x_interp += coord * coeffs[i];
461 }
462
463 let mut e_interp = DVector::zeros(dim);
465 for (i, error) in errors.iter().take(n_used).enumerate() {
466 e_interp += error * coeffs[i];
467 }
468
469 if n_used >= 2 {
471 self.validate_cosine(&e_interp, &errors[0], n_used)?;
472 }
473
474 let x_new = &x_interp - &e_interp;
476
477 Ok((x_new, coeffs[..n_used].to_vec(), n_used))
478 }
479
480 fn initialize_matrices(
482 &mut self,
483 errors: &VecDeque<DVector<f64>>,
484 n: usize,
485 ) -> Result<(), GdiisError> {
486 self.a_matrix = TriangularMatrix::new(n);
487 self.a_inv = TriangularMatrix::new(n);
488
489 for i in 0..n {
491 for j in 0..=i {
492 let dot = errors[i].dot(&errors[j]);
493 self.a_matrix.set(i, j, dot);
494 }
495 }
496
497 self.el2_min = (0..n)
499 .map(|i| self.a_matrix.get(i, i))
500 .fold(f64::INFINITY, f64::min);
501
502 if self.el2_min < 1e-30 {
503 return Err(GdiisError::SingularMatrix);
504 }
505
506 self.scale = 1.0 / self.el2_min;
507
508 self.a_matrix.scale(self.scale);
510
511 for i in 0..n {
513 self.a_inv.set(i, i, 1.0);
514 }
515
516 Ok(())
517 }
518
519 fn extend_matrices(
521 &mut self,
522 errors: &VecDeque<DVector<f64>>,
523 n: usize,
524 ) -> Result<(), GdiisError> {
525 let old_n = self.a_matrix.size();
526
527 if n <= old_n {
528 return Ok(());
529 }
530
531 if n > old_n + 1 {
533 return self.initialize_matrices(errors, n);
535 }
536
537 self.a_matrix.extend();
539 self.a_inv.extend();
540
541 let new_idx = n - 1;
543 for i in 0..n {
544 let dot = errors[i].dot(&errors[new_idx]);
545 self.a_matrix.set(new_idx, i, dot * self.scale);
546 }
547
548 let el2_new = self.a_matrix.get(new_idx, new_idx) / self.scale;
550 if el2_new < self.el2_min {
551 let old_scale = self.scale;
552 self.el2_min = el2_new;
553 self.scale = 1.0 / self.el2_min;
554
555 let ratio = old_scale / self.scale;
557 for i in 0..old_n {
558 for j in 0..=i {
559 let val = self.a_matrix.get(i, j);
560 self.a_matrix.set(i, j, val / ratio);
561 let inv_val = self.a_inv.get(i, j);
562 self.a_inv.set(i, j, inv_val * ratio);
563 }
564 }
565 }
566
567 self.a_inv.set(new_idx, new_idx, 1.0);
569
570 Ok(())
571 }
572
573 fn validate_coefficients(&self, coeffs: &[f64], n_used: usize) -> Result<(), GdiisError> {
580 if self.coeff_check == CoeffCheckMode::None {
581 return Ok(());
582 }
583
584 let c_max = 15.0;
585 let c_min = if self.coeff_check == CoeffCheckMode::Regular {
588 4.0 * (1.0 - c_max) } else {
590 1.0 - c_max };
592
593 let neg_sum: f64 = coeffs[..n_used]
595 .iter()
596 .filter(|&&c| c < 0.0)
597 .sum();
598
599 if neg_sum < c_min {
600 return Err(GdiisError::CoefficientCheckFailed {
601 reason: format!("Excessive extrapolation: neg_sum = {:.4} < {:.4}", neg_sum, c_min),
602 });
603 }
604
605 if coeffs[0].abs() < 1e-4 {
607 return Err(GdiisError::CoefficientCheckFailed {
608 reason: "Last point has no weight".to_string(),
609 });
610 }
611
612 if matches!(self.coeff_check, CoeffCheckMode::ForceRecent | CoeffCheckMode::Combined) {
614 let max_idx = coeffs[..n_used]
615 .iter()
616 .enumerate()
617 .max_by(|(_, a), (_, b)| a.abs().partial_cmp(&b.abs()).unwrap())
618 .map(|(i, _)| i)
619 .unwrap_or(0);
620
621 if max_idx != 0 {
622 if n_used > 2 && max_idx != 1 {
624 return Err(GdiisError::CoefficientCheckFailed {
626 reason: format!("Max coefficient at index {} (not recent)", max_idx),
627 });
628 } else if n_used == 2 {
629 let cof_max = coeffs[..n_used]
631 .iter()
632 .map(|c| c.abs())
633 .fold(0.0_f64, f64::max);
634 if cof_max > 1.0 && self.coeff_check == CoeffCheckMode::ForceRecent {
635 return Err(GdiisError::CoefficientCheckFailed {
637 reason: "Two-point extrapolation with wrong direction".to_string(),
638 });
639 }
640 }
641 }
642 }
643
644 Ok(())
645 }
646
647 fn validate_cosine(
649 &self,
650 residuum: &DVector<f64>,
651 last_error: &DVector<f64>,
652 n_used: usize,
653 ) -> Result<(), GdiisError> {
654 if self.cosine_check == CosineCheckMode::None {
655 return Ok(());
656 }
657
658 let s11 = residuum.norm_squared();
659 let s22 = last_error.norm_squared();
660 let s12 = residuum.dot(last_error);
661
662 if s11 * s22 < 1e-30 {
663 return Ok(()); }
665
666 let cos = s12 / (s11 * s22).sqrt();
667
668 let cos_limit = match self.cosine_check {
669 CosineCheckMode::None => return Ok(()),
670 CosineCheckMode::Zero => 0.0,
671 CosineCheckMode::Standard => 0.71,
672 CosineCheckMode::Variable => variable_cos_limit(n_used),
673 CosineCheckMode::Strict => 0.866,
674 };
675
676 if cos < cos_limit {
677 Err(GdiisError::CosineCheckFailed { cos, limit: cos_limit })
678 } else {
679 Ok(())
680 }
681 }
682}
683
684pub fn variable_cos_limit(n_used: usize) -> f64 {
686 match n_used {
687 2 => 0.97,
688 3 => 0.84,
689 4 => 0.71,
690 5 => 0.67,
691 6 => 0.62,
692 7 => 0.56,
693 8 => 0.49,
694 9 => 0.41,
695 _ => 0.0,
696 }
697}
698