1use crate::{
2 DFTProfile, FunctionalContribution, HelmholtzEnergyFunctional, WeightFunction,
3 WeightFunctionShape,
4};
5use feos_core::{FeosError, FeosResult, ReferenceSystem, Verbosity, log_iter, log_result};
6use nalgebra::{DMatrix, DVector, dvector};
7use ndarray::RemoveAxis;
8use ndarray::prelude::*;
9use petgraph::Directed;
10use petgraph::graph::Graph;
11use petgraph::visit::EdgeRef;
12use quantity::{SECOND, Time};
13use std::collections::VecDeque;
14use std::fmt;
15use std::ops::AddAssign;
16use std::time::{Duration, Instant};
17
18const DEFAULT_PARAMS_PICARD: PicardIteration = PicardIteration {
19 log: false,
20 max_iter: 500,
21 tol: 1e-11,
22 damping_coefficient: None,
23};
24const DEFAULT_PARAMS_ANDERSON_LOG: AndersonMixing = AndersonMixing {
25 log: true,
26 max_iter: 50,
27 tol: 1e-5,
28 damping_coefficient: 0.15,
29 mmax: 100,
30};
31const DEFAULT_PARAMS_ANDERSON: AndersonMixing = AndersonMixing {
32 log: false,
33 max_iter: 150,
34 tol: 1e-11,
35 damping_coefficient: 0.15,
36 mmax: 100,
37};
38const DEFAULT_PARAMS_NEWTON: Newton = Newton {
39 log: false,
40 max_iter: 50,
41 max_iter_gmres: 200,
42 tol: 1e-11,
43};
44
45#[derive(Clone, Copy, Debug)]
46struct PicardIteration {
47 log: bool,
48 max_iter: usize,
49 tol: f64,
50 damping_coefficient: Option<f64>,
51}
52
53#[derive(Clone, Copy, Debug)]
54struct AndersonMixing {
55 log: bool,
56 max_iter: usize,
57 tol: f64,
58 damping_coefficient: f64,
59 mmax: usize,
60}
61
62#[derive(Clone, Copy, Debug)]
63struct Newton {
64 log: bool,
65 max_iter: usize,
66 max_iter_gmres: usize,
67 tol: f64,
68}
69
70#[derive(Clone, Copy)]
71enum DFTAlgorithm {
72 PicardIteration(PicardIteration),
73 AndersonMixing(AndersonMixing),
74 Newton(Newton),
75}
76
77#[derive(Clone)]
79pub struct DFTSolver {
80 algorithms: Vec<DFTAlgorithm>,
81 pub verbosity: Verbosity,
82}
83
84impl Default for DFTSolver {
85 fn default() -> Self {
86 Self {
87 algorithms: vec![
88 DFTAlgorithm::AndersonMixing(DEFAULT_PARAMS_ANDERSON_LOG),
89 DFTAlgorithm::AndersonMixing(DEFAULT_PARAMS_ANDERSON),
90 ],
91 verbosity: Default::default(),
92 }
93 }
94}
95
96impl DFTSolver {
97 pub fn new(verbosity: Option<Verbosity>) -> Self {
98 Self {
99 algorithms: vec![],
100 verbosity: verbosity.unwrap_or_default(),
101 }
102 }
103
104 pub fn picard_iteration(
105 mut self,
106 log: Option<bool>,
107 max_iter: Option<usize>,
108 tol: Option<f64>,
109 damping_coefficient: Option<f64>,
110 ) -> Self {
111 let mut params = DEFAULT_PARAMS_PICARD;
112 params.log = log.unwrap_or(params.log);
113 params.max_iter = max_iter.unwrap_or(params.max_iter);
114 params.tol = tol.unwrap_or(params.tol);
115 params.damping_coefficient = damping_coefficient;
116 self.algorithms.push(DFTAlgorithm::PicardIteration(params));
117 self
118 }
119
120 pub fn anderson_mixing(
121 mut self,
122 log: Option<bool>,
123 max_iter: Option<usize>,
124 tol: Option<f64>,
125 damping_coefficient: Option<f64>,
126 mmax: Option<usize>,
127 ) -> Self {
128 let mut params = DEFAULT_PARAMS_ANDERSON;
129 params.log = log.unwrap_or(params.log);
130 params.max_iter = max_iter.unwrap_or(params.max_iter);
131 params.tol = tol.unwrap_or(params.tol);
132 params.damping_coefficient = damping_coefficient.unwrap_or(params.damping_coefficient);
133 params.mmax = mmax.unwrap_or(params.mmax);
134 self.algorithms.push(DFTAlgorithm::AndersonMixing(params));
135 self
136 }
137
138 pub fn newton(
139 mut self,
140 log: Option<bool>,
141 max_iter: Option<usize>,
142 max_iter_gmres: Option<usize>,
143 tol: Option<f64>,
144 ) -> Self {
145 let mut params = DEFAULT_PARAMS_NEWTON;
146 params.log = log.unwrap_or(params.log);
147 params.max_iter = max_iter.unwrap_or(params.max_iter);
148 params.max_iter_gmres = max_iter_gmres.unwrap_or(params.max_iter_gmres);
149 params.tol = tol.unwrap_or(params.tol);
150 self.algorithms.push(DFTAlgorithm::Newton(params));
151 self
152 }
153}
154
155#[derive(Clone)]
157pub struct DFTSolverLog {
158 verbosity: Verbosity,
159 start_time: Instant,
160 residual: Vec<f64>,
161 time: Vec<Duration>,
162 solver: Vec<&'static str>,
163}
164
165impl DFTSolverLog {
166 pub(crate) fn new(verbosity: Verbosity) -> Self {
167 log_iter!(
168 verbosity,
169 "solver | iter | time | residual "
170 );
171 Self {
172 verbosity,
173 start_time: Instant::now(),
174 residual: Vec::new(),
175 time: Vec::new(),
176 solver: Vec::new(),
177 }
178 }
179
180 fn add_residual(&mut self, solver: &'static str, iteration: usize, residual: f64) {
181 if iteration == 0 {
182 log_iter!(self.verbosity, "{:-<59}", "");
183 }
184 self.solver.push(solver);
185 self.residual.push(residual);
186 let time = self.start_time.elapsed();
187 self.time.push(self.start_time.elapsed());
188 log_iter!(
189 self.verbosity,
190 "{:22} | {:>4} | {:7.3} | {:.6e}",
191 solver,
192 iteration,
193 time.as_secs_f64() * SECOND,
194 residual,
195 );
196 }
197
198 pub fn residual(&self) -> ArrayView1<'_, f64> {
199 (&self.residual).into()
200 }
201
202 pub fn time(&self) -> Time<Array1<f64>> {
203 self.time.iter().map(|t| t.as_secs_f64() * SECOND).collect()
204 }
205
206 pub fn solver(&self) -> &[&'static str] {
207 &self.solver
208 }
209}
210
211impl<D: Dimension, F: HelmholtzEnergyFunctional> DFTProfile<D, F>
212where
213 D::Larger: Dimension<Smaller = D>,
214 <D::Larger as Dimension>::Larger: Dimension<Smaller = D::Larger>,
215{
216 pub(crate) fn call_solver(
217 &mut self,
218 rho: &mut Array<f64, D::Larger>,
219 rho_bulk: &mut Array1<f64>,
220 solver: &DFTSolver,
221 debug: bool,
222 ) -> FeosResult<()> {
223 let mut converged = false;
224 let mut iterations = 0;
225 let mut log = DFTSolverLog::new(solver.verbosity);
226 for algorithm in &solver.algorithms {
227 let (conv, iter) = match algorithm {
228 DFTAlgorithm::PicardIteration(picard) => {
229 self.solve_picard(*picard, rho, rho_bulk, &mut log)
230 }
231 DFTAlgorithm::AndersonMixing(anderson) => {
232 self.solve_anderson(*anderson, rho, rho_bulk, &mut log)
233 }
234 DFTAlgorithm::Newton(newton) => self.solve_newton(*newton, rho, rho_bulk, &mut log),
235 }?;
236 converged = conv;
237 iterations += iter;
238 }
239 self.solver_log = Some(log);
240 if converged {
241 log_result!(solver.verbosity, "DFT solved in {} iterations", iterations);
242 } else if debug {
243 log_result!(
244 solver.verbosity,
245 "DFT not converged in {} iterations",
246 iterations
247 );
248 } else {
249 return Err(FeosError::NotConverged(String::from("DFT")));
250 }
251 Ok(())
252 }
253
254 fn solve_picard(
255 &self,
256 picard: PicardIteration,
257 rho: &mut Array<f64, D::Larger>,
258 rho_bulk: &mut Array1<f64>,
259 log: &mut DFTSolverLog,
260 ) -> FeosResult<(bool, usize)> {
261 let solver = if picard.log {
262 "Picard iteration (log)"
263 } else {
264 "Picard iteration"
265 };
266
267 for k in 0..picard.max_iter {
268 let (res, res_bulk, res_norm, _, _) =
270 self.euler_lagrange_equation(&*rho, &*rho_bulk, picard.log)?;
271 log.add_residual(solver, k, res_norm);
272
273 if res_norm < picard.tol {
275 return Ok((true, k));
276 }
277
278 let damping_coefficient = picard.damping_coefficient.map_or_else(
280 || self.line_search(rho, &res, rho_bulk, res_norm, picard.log),
281 Ok,
282 )?;
283
284 if picard.log {
286 *rho *= &(&res * damping_coefficient).mapv(f64::exp);
287 *rho_bulk *= &(&res_bulk * damping_coefficient).mapv(f64::exp);
288 } else {
289 *rho += &(&res * damping_coefficient);
290 *rho_bulk += &(&res_bulk * damping_coefficient);
291 }
292 }
293 Ok((false, picard.max_iter))
294 }
295
296 fn line_search(
297 &self,
298 rho: &Array<f64, D::Larger>,
299 delta_rho: &Array<f64, D::Larger>,
300 rho_bulk: &Array1<f64>,
301 res0: f64,
302 logarithm: bool,
303 ) -> FeosResult<f64> {
304 let mut alpha = 2.0;
305
306 for _ in 0..8 {
308 alpha *= 0.5;
309
310 let rho_new = if logarithm {
312 rho * (alpha * delta_rho).mapv(f64::exp)
313 } else {
314 rho + alpha * delta_rho
315 };
316 let Ok((_, _, res2, _, _)) =
317 self.euler_lagrange_equation(&rho_new, rho_bulk, logarithm)
318 else {
319 continue;
320 };
321 if res2 > res0 {
322 continue;
323 }
324
325 let rho_new = if logarithm {
327 rho * (0.5 * alpha * delta_rho).mapv(f64::exp)
328 } else {
329 rho + 0.5 * alpha * delta_rho
330 };
331 let Ok((_, _, res1, _, _)) =
332 self.euler_lagrange_equation(&rho_new, rho_bulk, logarithm)
333 else {
334 continue;
335 };
336
337 let mut alpha_opt = if res2 - 2.0 * res1 + res0 != 0.0 {
339 alpha * 0.25 * (res2 - 4.0 * res1 + 3.0 * res0) / (res2 - 2.0 * res1 + res0)
340 } else {
341 continue;
342 };
343
344 if alpha_opt <= 0.0 {
346 alpha_opt = if res1 < res2 { 0.5 * alpha } else { alpha };
347 }
348
349 if alpha_opt > alpha {
351 alpha_opt = alpha;
352 }
353 alpha = alpha_opt;
354 break;
355 }
356 Ok(alpha)
358 }
359
360 fn solve_anderson(
361 &self,
362 anderson: AndersonMixing,
363 rho: &mut Array<f64, D::Larger>,
364 rho_bulk: &mut Array1<f64>,
365 log: &mut DFTSolverLog,
366 ) -> FeosResult<(bool, usize)> {
367 let solver = if anderson.log {
368 "Anderson mixing (log)"
369 } else {
370 "Anderson mixing"
371 };
372
373 let mut resm = VecDeque::with_capacity(anderson.mmax);
374 let mut rhom = VecDeque::with_capacity(anderson.mmax);
375 let mut r;
376 let mut alpha;
377
378 for k in 0..anderson.max_iter {
379 if resm.len() == anderson.mmax {
381 resm.pop_front();
382 rhom.pop_front();
383 }
384 let m = resm.len() + 1;
385
386 let (res, res_bulk, res_norm, _, _) =
388 self.euler_lagrange_equation(&*rho, &*rho_bulk, anderson.log)?;
389 log.add_residual(solver, k, res_norm);
390
391 if res_norm < anderson.tol {
393 return Ok((true, k));
394 }
395
396 resm.push_back((res, res_bulk, res_norm));
398 if anderson.log {
399 rhom.push_back((rho.mapv(f64::ln), rho_bulk.mapv(f64::ln)));
400 } else {
401 rhom.push_back((rho.clone(), rho_bulk.clone()));
402 }
403
404 r = DMatrix::from_fn(m + 1, m + 1, |i, j| match (i == m, j == m) {
406 (false, false) => {
407 let (resi, resi_bulk, _) = &resm[i];
408 let (resj, resj_bulk, _) = &resm[j];
409 (resi * resj).sum() + (resi_bulk * resj_bulk).sum()
410 }
411 (true, true) => 0.0,
412 _ => 1.0,
413 });
414 alpha = DVector::zeros(m + 1);
415 alpha[m] = 1.0;
416 let alpha = r.lu().solve(&alpha);
417 let alpha = alpha.ok_or(FeosError::Error("alpha matrix is not invertible".into()))?;
418
419 rho.fill(0.0);
421 rho_bulk.fill(0.0);
422 for i in 0..m {
423 let (rhoi, rhoi_bulk) = &rhom[i];
424 let (resi, resi_bulk, _) = &resm[i];
425 *rho += &(alpha[i] * (rhoi + &(anderson.damping_coefficient * resi)));
426 *rho_bulk +=
427 &(alpha[i] * (rhoi_bulk + &(anderson.damping_coefficient * resi_bulk)));
428 }
429 if anderson.log {
430 rho.mapv_inplace(f64::exp);
431 rho_bulk.mapv_inplace(f64::exp);
432 } else {
433 rho.mapv_inplace(f64::abs);
434 rho_bulk.mapv_inplace(f64::abs);
435 }
436 }
437 Ok((false, anderson.max_iter))
438 }
439
440 fn solve_newton(
441 &self,
442 newton: Newton,
443 rho: &mut Array<f64, D::Larger>,
444 rho_bulk: &mut Array1<f64>,
445 log: &mut DFTSolverLog,
446 ) -> FeosResult<(bool, usize)> {
447 let solver = if newton.log { "Newton (log)" } else { "Newton" };
448 for k in 0..newton.max_iter {
449 let (res, _, res_norm, exp_dfdrho, rho_p) =
451 self.euler_lagrange_equation(rho, rho_bulk, newton.log)?;
452 log.add_residual(solver, k, res_norm);
453
454 if res_norm < newton.tol {
456 return Ok((true, k));
457 }
458
459 let second_partial_derivatives = self.second_partial_derivatives(rho)?;
461
462 let rhs = |delta_rho: &_| {
464 let mut delta_functional_derivative =
465 self.delta_functional_derivative(delta_rho, &second_partial_derivatives);
466 delta_functional_derivative
467 .outer_iter_mut()
468 .zip(self.bulk.eos.m().iter())
469 .for_each(|(mut q, &m)| q /= m);
470 let delta_i = self.delta_bond_integrals(&exp_dfdrho, &delta_functional_derivative);
471 let rho = if newton.log { &*rho } else { &rho_p };
472 delta_rho + (delta_functional_derivative - delta_i) * rho
473 };
474
475 let lhs = if newton.log { &*rho * res } else { res };
477 *rho += &Self::gmres(rhs, &lhs, newton.max_iter_gmres, newton.tol * 1e-2, log)?;
478 rho.mapv_inplace(f64::abs);
479 rho_bulk.mapv_inplace(f64::abs);
480 }
481
482 Ok((false, newton.max_iter))
483 }
484
485 pub(crate) fn gmres<R>(
486 rhs: R,
487 r0: &Array<f64, D::Larger>,
488 max_iter: usize,
489 tol: f64,
490 log: &mut DFTSolverLog,
491 ) -> FeosResult<Array<f64, D::Larger>>
492 where
493 R: Fn(&Array<f64, D::Larger>) -> Array<f64, D::Larger>,
494 {
495 let mut v = Vec::with_capacity(max_iter);
497 let mut h: Array2<f64> = Array::zeros([max_iter + 1; 2]);
498 let mut c: Array1<f64> = Array::zeros(max_iter + 1);
499 let mut s: Array1<f64> = Array::zeros(max_iter + 1);
500 let mut gamma: Array1<f64> = Array::zeros(max_iter + 1);
501
502 gamma[0] = (r0 * r0).sum().sqrt();
503 v.push(r0 / gamma[0]);
504 log.add_residual("GMRES", 0, gamma[0]);
505
506 let mut iter = 0;
507 for j in 0..max_iter {
508 let mut q = rhs(&v[j]);
510
511 v.iter()
513 .enumerate()
514 .for_each(|(i, v_i)| h[(i, j)] = (v_i * &q).sum());
515
516 v.iter()
518 .enumerate()
519 .for_each(|(i, v_i)| q -= &(h[(i, j)] * v_i));
520 h[(j + 1, j)] = (&q * &q).sum().sqrt();
521
522 if j > 0 {
524 for i in 0..=j - 1 {
525 let temp = c[i + 1] * h[(i, j)] + s[i + 1] * h[(i + 1, j)];
526 h[(i + 1, j)] = -s[i + 1] * h[(i, j)] + c[i + 1] * h[(i + 1, j)];
527 h[(i, j)] = temp;
528 }
529 }
530
531 let beta = (h[(j, j)] * h[(j, j)] + h[(j + 1, j)] * h[(j + 1, j)]).sqrt();
533 s[j + 1] = h[(j + 1, j)] / beta;
534 c[j + 1] = h[(j, j)] / beta;
535 h[(j, j)] = beta;
536 gamma[j + 1] = -s[j + 1] * gamma[j];
537 gamma[j] *= c[j + 1];
538
539 log.add_residual("GMRES", j + 1, gamma[j + 1].abs());
541 if gamma[j + 1].abs() >= tol && j + 1 < max_iter {
542 v.push(q / h[(j + 1, j)]);
543 iter += 1;
544 } else {
545 break;
546 }
547 }
548 let mut x = Array::zeros(r0.raw_dim());
550 let mut y = Array::zeros(iter + 1);
551 for i in (0..=iter).rev() {
552 y[i] = (gamma[i] - (i + 1..=iter).map(|k| h[(i, k)] * y[k]).sum::<f64>()) / h[(i, i)];
553 }
554 v.iter().zip(y).for_each(|(v, y)| x += &(y * v));
555 Ok(x)
556 }
557
558 pub(crate) fn second_partial_derivatives(
559 &self,
560 density: &Array<f64, D::Larger>,
561 ) -> FeosResult<Vec<Array<f64, <D::Larger as Dimension>::Larger>>> {
562 let temperature = self.temperature.to_reduced();
563 let contributions = self.bulk.eos.contributions();
564 let weighted_densities = self.convolver.weighted_densities(density);
565 let mut second_partial_derivatives = Vec::new();
566 for (c, wd) in contributions.into_iter().zip(&weighted_densities) {
567 let nwd = wd.shape()[0];
568 let ngrid = wd.len() / nwd;
569 let mut phi = Array::zeros(density.raw_dim().remove_axis(Axis(0)));
570 let mut pd = Array::zeros(wd.raw_dim());
571 let dim = wd.shape();
572 let dim: Vec<_> = std::iter::once(&nwd).chain(dim).cloned().collect();
573 let mut pd2 = Array::zeros(dim).into_dimensionality().unwrap();
574 c.second_partial_derivatives(
575 temperature,
576 wd.view().into_shape_with_order((nwd, ngrid)).unwrap(),
577 phi.view_mut().into_shape_with_order(ngrid).unwrap(),
578 pd.view_mut().into_shape_with_order((nwd, ngrid)).unwrap(),
579 pd2.view_mut()
580 .into_shape_with_order((nwd, nwd, ngrid))
581 .unwrap(),
582 )?;
583 second_partial_derivatives.push(pd2);
584 }
585 Ok(second_partial_derivatives)
586 }
587
588 pub(crate) fn delta_functional_derivative(
589 &self,
590 delta_density: &Array<f64, D::Larger>,
591 second_partial_derivatives: &[Array<f64, <D::Larger as Dimension>::Larger>],
592 ) -> Array<f64, D::Larger> {
593 let delta_weighted_densities = self.convolver.weighted_densities(delta_density);
594 let delta_partial_derivatives: Vec<_> = second_partial_derivatives
595 .iter()
596 .zip(delta_weighted_densities)
597 .map(|(pd2, wd)| {
598 let mut delta_partial_derivatives =
599 Array::zeros(pd2.raw_dim().remove_axis(Axis(0)));
600 let n = wd.shape()[0];
601 for i in 0..n {
602 for j in 0..n {
603 delta_partial_derivatives
604 .index_axis_mut(Axis(0), i)
605 .add_assign(
606 &(&pd2.index_axis(Axis(0), i).index_axis(Axis(0), j)
607 * &wd.index_axis(Axis(0), j)),
608 );
609 }
610 }
611 delta_partial_derivatives
612 })
613 .collect();
614 self.convolver
615 .functional_derivative(&delta_partial_derivatives)
616 }
617
618 pub(crate) fn delta_bond_integrals(
619 &self,
620 exponential: &Array<f64, D::Larger>,
621 delta_functional_derivative: &Array<f64, D::Larger>,
622 ) -> Array<f64, D::Larger> {
623 let temperature = self.temperature.to_reduced();
624
625 let bond_lengths = self.bulk.eos.bond_lengths(temperature).into_edge_type();
627 let mut bond_weight_functions = bond_lengths.map(
628 |_, _| (),
629 |_, &l| WeightFunction::new_scaled(dvector![l], WeightFunctionShape::Delta),
630 );
631 for n in bond_lengths.node_indices() {
632 for e in bond_lengths.edges(n) {
633 bond_weight_functions.add_edge(
634 e.target(),
635 e.source(),
636 WeightFunction::new_scaled(dvector![*e.weight()], WeightFunctionShape::Delta),
637 );
638 }
639 }
640
641 let mut i_graph: Graph<_, Option<Array<f64, D>>, Directed> =
642 bond_weight_functions.map(|_, _| (), |_, _| None);
643 let mut delta_i_graph: Graph<_, Option<Array<f64, D>>, Directed> =
644 bond_weight_functions.map(|_, _| (), |_, _| None);
645
646 let bonds = i_graph.edge_count();
647 let mut calc = 0;
648
649 while calc < bonds {
651 let mut edge_id = None;
652 let mut i1 = None;
653 let mut delta_i1 = None;
654
655 'nodes: for node in i_graph.node_indices() {
657 for edge in i_graph.edges(node) {
658 if edge.weight().is_some() {
660 continue;
661 }
662
663 let edges = i_graph
665 .edges(edge.target())
666 .filter(|e| e.target().index() != node.index());
667 let delta_edges = delta_i_graph
668 .edges(edge.target())
669 .filter(|e| e.target().index() != node.index());
670 if edges.clone().all(|e| e.weight().is_some()) {
671 edge_id = Some(edge.id());
672 let i0 = edges.fold(
673 exponential
674 .index_axis(Axis(0), edge.target().index())
675 .to_owned(),
676 |acc: Array<f64, _>, e| acc * e.weight().as_ref().unwrap(),
677 );
678 let delta_i0 = delta_edges.fold(
679 -&delta_functional_derivative
680 .index_axis(Axis(0), edge.target().index()),
681 |acc: Array<f64, _>, delta_e| acc + delta_e.weight().as_ref().unwrap(),
682 ) * &i0;
683 i1 = Some(
684 self.convolver
685 .convolve(i0, &bond_weight_functions[edge.id()]),
686 );
687 delta_i1 = Some(
688 (self
689 .convolver
690 .convolve(delta_i0, &bond_weight_functions[edge.id()])
691 / i1.as_ref().unwrap())
692 .mapv(|x| if x.is_finite() { x } else { 0.0 }),
693 );
694 break 'nodes;
695 }
696 }
697 }
698 if let Some(edge_id) = edge_id {
699 i_graph[edge_id] = i1;
700 delta_i_graph[edge_id] = delta_i1;
701 calc += 1;
702 } else {
703 panic!("Cycle in molecular structure detected!")
704 }
705 }
706
707 let mut delta_i = Array::zeros(exponential.raw_dim());
708 for node in delta_i_graph.node_indices() {
709 for edge in delta_i_graph.edges(node) {
710 delta_i
711 .index_axis_mut(Axis(0), node.index())
712 .add_assign(edge.weight().as_ref().unwrap());
713 }
714 }
715
716 delta_i
717 }
718}
719
720impl fmt::Display for DFTAlgorithm {
721 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
722 match self {
723 Self::PicardIteration(picard) => write!(f, "{picard:?}"),
724 Self::AndersonMixing(anderson) => write!(f, "{anderson:?}"),
725 Self::Newton(newton) => write!(f, "{newton:?}"),
726 }
727 }
728}
729
730impl fmt::Display for DFTSolver {
731 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
732 writeln!(f, "DFTSolver(")?;
733 for algorithm in &self.algorithms {
734 writeln!(f, " {algorithm}")?;
735 }
736 writeln!(f, ")")
737 }
738}
739
740impl DFTSolver {
741 pub fn _repr_markdown_(&self) -> String {
742 let mut res = String::from("|solver|max_iter|tol|\n|-|-:|-:|");
743 for algorithm in &self.algorithms {
744 let (solver, max_iter, tol) = match algorithm {
745 DFTAlgorithm::PicardIteration(picard) => (
746 format!(
747 "Picard iteration ({}{})",
748 if picard.log { "log, " } else { "" },
749 match picard.damping_coefficient {
750 None => "line search".into(),
751 Some(damping_coefficient) =>
752 format!("damping_coefficient={damping_coefficient}"),
753 }
754 ),
755 picard.max_iter,
756 picard.tol,
757 ),
758 DFTAlgorithm::AndersonMixing(anderson) => (
759 format!(
760 "Anderson mixing ({}damping_coefficient={}, mmax={})",
761 if anderson.log { "log, " } else { "" },
762 anderson.damping_coefficient,
763 anderson.mmax
764 ),
765 anderson.max_iter,
766 anderson.tol,
767 ),
768 DFTAlgorithm::Newton(newton) => (
769 format!(
770 "Newton ({}max_iter_gmres={})",
771 if newton.log { "log, " } else { "" },
772 newton.max_iter_gmres
773 ),
774 newton.max_iter,
775 newton.tol,
776 ),
777 };
778 res += &format!("\n|{solver}|{max_iter}|{tol:e}|");
779 }
780 res
781 }
782}