1use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
23use std::sync::Arc;
24
25use super::ldu_matrix::LduMatrix;
26use super::solvers::conjugate_gradient::conjugate_gradient;
27use super::solvers::gamg::gamg;
28use super::solvers::gauss_seidel::gauss_seidel;
29use crate::fields::boundary::bc::PatchField;
30use crate::fields::field::Field;
31use crate::fields::vol_field::VolScalarField;
32use crate::mesh::fv_mesh::FvMesh;
33
34pub struct FvMatrix {
42 pub mesh: Arc<FvMesh>,
43 pub ldu: LduMatrix,
44 pub source: Field<f64>,
46}
47
48#[derive(Debug, Clone, Copy)]
50pub struct SolverSettings {
51 pub tolerance: f64,
52 pub max_iter: usize,
53}
54
55impl Default for SolverSettings {
56 fn default() -> Self {
57 Self {
58 tolerance: 1e-7,
59 max_iter: 1000,
60 }
61 }
62}
63
64#[derive(Debug, Clone, Copy)]
66pub struct SolverPerformance {
67 pub n_iterations: usize,
68 pub final_residual: f64,
69 pub converged: bool,
70}
71
72impl FvMatrix {
73 pub fn new(mesh: Arc<FvMesh>) -> Self {
75 let owner = mesh.owner[..mesh.n_internal_faces].to_vec();
76 let neighbour = mesh.neighbour.to_vec();
77 let n_cells = mesh.n_cells;
78 Self {
79 mesh,
80 ldu: LduMatrix::new(n_cells, owner, neighbour),
81 source: Field::zeros(n_cells),
82 }
83 }
84
85 pub fn solve(
90 &self,
91 name: impl Into<String>,
92 settings: SolverSettings,
93 ) -> (VolScalarField, SolverPerformance) {
94 let mut x = vec![0.0_f64; self.mesh.n_cells];
95 let b: Vec<f64> = self.source.iter().copied().collect();
96 let (iters, res) =
97 gauss_seidel(&self.ldu, &b, &mut x, settings.tolerance, settings.max_iter);
98
99 let boundary = self
100 .mesh
101 .patches
102 .iter()
103 .map(|p| PatchField::zero_gradient(p.size))
104 .collect();
105 let field = VolScalarField::new(name, self.mesh.clone(), Field::new(x), boundary);
106 let perf = SolverPerformance {
107 n_iterations: iters,
108 final_residual: res,
109 converged: res < settings.tolerance,
110 };
111 (field, perf)
112 }
113
114 pub fn solve_cg(
127 &self,
128 name: impl Into<String>,
129 settings: SolverSettings,
130 ) -> (VolScalarField, SolverPerformance) {
131 self.solve_cg_inner(name, None, settings)
132 }
133
134 pub fn solve_cg_with_guess(
144 &self,
145 name: impl Into<String>,
146 initial: &VolScalarField,
147 settings: SolverSettings,
148 ) -> (VolScalarField, SolverPerformance) {
149 self.solve_cg_inner(name, Some(initial.internal.as_slice()), settings)
150 }
151
152 fn solve_cg_inner(
153 &self,
154 name: impl Into<String>,
155 x0: Option<&[f64]>,
156 settings: SolverSettings,
157 ) -> (VolScalarField, SolverPerformance) {
158 let b: Vec<f64> = self.source.iter().copied().collect();
159 let (x, perf) = conjugate_gradient(&self.ldu, &b, x0, &settings);
160
161 let boundary = self
162 .mesh
163 .patches
164 .iter()
165 .map(|p| PatchField::zero_gradient(p.size))
166 .collect();
167 let field = VolScalarField::new(name, self.mesh.clone(), Field::new(x), boundary);
168 (field, perf)
169 }
170
171 pub fn solve_gamg(
181 &self,
182 name: impl Into<String>,
183 settings: SolverSettings,
184 ) -> (VolScalarField, SolverPerformance) {
185 self.solve_gamg_inner(name, None, settings)
186 }
187
188 pub fn solve_gamg_with_guess(
193 &self,
194 name: impl Into<String>,
195 initial: &VolScalarField,
196 settings: SolverSettings,
197 ) -> (VolScalarField, SolverPerformance) {
198 self.solve_gamg_inner(name, Some(initial.internal.as_slice()), settings)
199 }
200
201 fn solve_gamg_inner(
202 &self,
203 name: impl Into<String>,
204 x0: Option<&[f64]>,
205 settings: SolverSettings,
206 ) -> (VolScalarField, SolverPerformance) {
207 let b: Vec<f64> = self.source.iter().copied().collect();
208 let (x, perf) = gamg(&self.ldu, &b, x0, &settings);
209
210 let boundary = self
211 .mesh
212 .patches
213 .iter()
214 .map(|p| PatchField::zero_gradient(p.size))
215 .collect();
216 let field = VolScalarField::new(name, self.mesh.clone(), Field::new(x), boundary);
217 (field, perf)
218 }
219
220 pub fn add_to_diag(&mut self, coeff: &Field<f64>) {
224 for (d, &c) in self.ldu.diag.iter_mut().zip(coeff.iter()) {
225 *d += c;
226 }
227 }
228
229 pub fn add_to_source(&mut self, term: &Field<f64>) {
231 self.source += term.clone();
232 }
233
234 pub fn add_face_coeff(&mut self, face: usize, coeff: f64) {
236 let o = self.ldu.owner[face];
237 let n = self.ldu.neighbour[face];
238 self.ldu.upper[face] -= coeff;
240 self.ldu.lower[face] -= coeff;
241 self.ldu.diag[o] += coeff;
242 self.ldu.diag[n] += coeff;
243 }
244
245 pub fn set_reference(&mut self, cell: usize, value: f64) {
251 self.ldu.diag[cell] += 1e30;
252 self.source[cell] += 1e30 * value;
253 }
254
255 pub fn a_field(&self) -> VolScalarField {
259 let boundary = self
260 .mesh
261 .patches
262 .iter()
263 .map(|p| PatchField::zero_gradient(p.size))
264 .collect();
265 VolScalarField::new(
266 "A",
267 self.mesh.clone(),
268 Field::new(self.ldu.diag.clone()),
269 boundary,
270 )
271 }
272
273 pub fn h_field(&self, x: &VolScalarField) -> VolScalarField {
278 let n = self.mesh.n_cells;
279 let mut h = vec![0.0_f64; n];
280 for c in 0..n {
281 h[c] = self.source[c];
282 }
283 for f in 0..self.mesh.n_internal_faces {
284 let o = self.ldu.owner[f];
285 let nb = self.ldu.neighbour[f];
286 h[o] -= x.internal[nb] * self.ldu.upper[f];
287 h[nb] -= x.internal[o] * self.ldu.lower[f];
288 }
289 let boundary = self
290 .mesh
291 .patches
292 .iter()
293 .map(|p| PatchField::zero_gradient(p.size))
294 .collect();
295 VolScalarField::new("H", self.mesh.clone(), Field::new(h), boundary)
296 }
297}
298
299impl Add for FvMatrix {
302 type Output = Self;
303 fn add(mut self, rhs: Self) -> Self {
304 for (a, b) in self.ldu.diag.iter_mut().zip(&rhs.ldu.diag) {
305 *a += b;
306 }
307 for (a, b) in self.ldu.lower.iter_mut().zip(&rhs.ldu.lower) {
308 *a += b;
309 }
310 for (a, b) in self.ldu.upper.iter_mut().zip(&rhs.ldu.upper) {
311 *a += b;
312 }
313 self.source += rhs.source;
314 self
315 }
316}
317
318impl Sub for FvMatrix {
319 type Output = Self;
320 fn sub(mut self, rhs: Self) -> Self {
321 for (a, b) in self.ldu.diag.iter_mut().zip(&rhs.ldu.diag) {
322 *a -= b;
323 }
324 for (a, b) in self.ldu.lower.iter_mut().zip(&rhs.ldu.lower) {
325 *a -= b;
326 }
327 for (a, b) in self.ldu.upper.iter_mut().zip(&rhs.ldu.upper) {
328 *a -= b;
329 }
330 self.source -= rhs.source;
331 self
332 }
333}
334
335impl Neg for FvMatrix {
336 type Output = Self;
337 fn neg(mut self) -> Self {
338 for x in self.ldu.diag.iter_mut() {
339 *x = -*x;
340 }
341 for x in self.ldu.lower.iter_mut() {
342 *x = -*x;
343 }
344 for x in self.ldu.upper.iter_mut() {
345 *x = -*x;
346 }
347 self.source = -self.source;
348 self
349 }
350}
351
352impl AddAssign for FvMatrix {
353 fn add_assign(&mut self, rhs: Self) {
354 for (a, b) in self.ldu.diag.iter_mut().zip(&rhs.ldu.diag) {
355 *a += b;
356 }
357 for (a, b) in self.ldu.lower.iter_mut().zip(&rhs.ldu.lower) {
358 *a += b;
359 }
360 for (a, b) in self.ldu.upper.iter_mut().zip(&rhs.ldu.upper) {
361 *a += b;
362 }
363 self.source += rhs.source;
364 }
365}
366
367impl SubAssign for FvMatrix {
368 fn sub_assign(&mut self, rhs: Self) {
369 for (a, b) in self.ldu.diag.iter_mut().zip(&rhs.ldu.diag) {
370 *a -= b;
371 }
372 for (a, b) in self.ldu.lower.iter_mut().zip(&rhs.ldu.lower) {
373 *a -= b;
374 }
375 for (a, b) in self.ldu.upper.iter_mut().zip(&rhs.ldu.upper) {
376 *a -= b;
377 }
378 self.source -= rhs.source;
379 }
380}
381
382#[cfg(test)]
385mod tests {
386 use super::*;
387 use crate::mesh::fv_mesh::{BoundaryPatch, FvMeshBuilder, PatchKind};
388 use crate::primitives::Vector3;
389 use approx::assert_relative_eq;
390
391 fn unit_mesh() -> Arc<FvMesh> {
392 Arc::new(
393 FvMeshBuilder::new()
394 .n_cells(2)
395 .n_internal_faces(1)
396 .owner(vec![0, 1, 0])
397 .neighbour(vec![1])
398 .patches(vec![
399 BoundaryPatch::new("right", 1, 1, PatchKind::Wall),
400 BoundaryPatch::new("left", 2, 1, PatchKind::Wall),
401 ])
402 .cell_volumes(vec![1.0, 1.0])
403 .cell_centres(vec![
404 Vector3::new(0.25, 0.0, 0.0),
405 Vector3::new(0.75, 0.0, 0.0),
406 ])
407 .face_area_vectors(vec![
408 Vector3::new(1.0, 0.0, 0.0),
409 Vector3::new(1.0, 0.0, 0.0),
410 Vector3::new(-1.0, 0.0, 0.0),
411 ])
412 .face_centres(vec![
413 Vector3::new(0.5, 0.0, 0.0),
414 Vector3::new(1.0, 0.0, 0.0),
415 Vector3::new(0.0, 0.0, 0.0),
416 ])
417 .build()
418 .unwrap(),
419 )
420 }
421
422 #[test]
423 fn solve_diagonal_system() {
424 let mesh = unit_mesh();
425 let mut mat = FvMatrix::new(mesh);
426
427 mat.ldu.diag[0] = 3.0;
429 mat.ldu.diag[1] = 5.0;
430 mat.source = Field::new(vec![6.0, 10.0]);
431
432 let (phi, perf) = mat.solve("phi", SolverSettings::default());
433 assert!(
434 perf.converged,
435 "did not converge: residual = {}",
436 perf.final_residual
437 );
438 assert_relative_eq!(phi.internal[0], 2.0, epsilon = 1e-6);
439 assert_relative_eq!(phi.internal[1], 2.0, epsilon = 1e-6);
440 }
441
442 #[test]
443 fn add_face_coeff_symmetrises_diag() {
444 let mesh = unit_mesh();
445 let mut mat = FvMatrix::new(mesh);
446 mat.add_face_coeff(0, 1.0);
447 assert_relative_eq!(mat.ldu.diag[0], 1.0);
449 assert_relative_eq!(mat.ldu.diag[1], 1.0);
450 assert_relative_eq!(mat.ldu.upper[0], -1.0);
451 assert_relative_eq!(mat.ldu.lower[0], -1.0);
452 }
453}