1use std::ops::{Add, AddAssign, Neg, Sub, SubAssign};
23use std::sync::Arc;
24
25use super::fv_matrix::{SolverPerformance, SolverSettings};
26use super::ldu_matrix::LduMatrix;
27use super::solvers::gauss_seidel::gauss_seidel as gs;
28use super::solvers::krylov_solve::{krylov_solve, KrylovMethod, KrylovOptions};
29use crate::fields::boundary::bc::PatchField;
30use crate::fields::field::Field;
31use crate::fields::vol_field::{VolScalarField, VolVectorField};
32use crate::mesh::fv_mesh::FvMesh;
33use crate::primitives::Vector3;
34
35#[derive(Debug, Clone)]
44pub struct FvVectorMatrix {
45 pub mesh: Arc<FvMesh>,
47 pub ldu: LduMatrix,
49 pub source: Field<Vector3>,
51}
52
53impl FvVectorMatrix {
54 pub fn new(mesh: Arc<FvMesh>) -> Self {
64 let n_cells = mesh.n_cells;
65 let mut owner = mesh.owner[..mesh.n_internal_faces].to_vec();
66 let mut neighbour = mesh.neighbour.clone();
67 for cc in &mesh.cyclic_couplings {
68 owner.push(cc.owner);
69 neighbour.push(cc.neighbour);
70 }
71 for cc in &mesh.ami_couplings {
74 for w in &cc.weights {
75 owner.push(cc.target_cell);
76 neighbour.push(w.source_cell);
77 }
78 }
79 Self {
80 ldu: LduMatrix::new(n_cells, owner, neighbour),
81 source: Field::from_fn(n_cells, |_| Vector3::ZERO),
82 mesh,
83 }
84 }
85
86 pub fn add_to_diag(&mut self, coeff: &Field<f64>) {
88 for c in 0..self.mesh.n_cells {
89 self.ldu.diag[c] += coeff[c];
90 }
91 }
92
93 pub fn add_to_source(&mut self, term: &Field<Vector3>) {
95 for c in 0..self.mesh.n_cells {
96 self.source[c] = self.source[c] + term[c];
97 }
98 }
99
100 pub fn set_reference(&mut self, cell: usize, value: Vector3) {
102 self.ldu.diag[cell] += 1e30;
103 self.source[cell] = self.source[cell] + value * 1e30;
104 }
105
106 pub fn a_field(&self) -> VolScalarField {
110 let mesh = self.mesh.clone();
111 let boundary = mesh
112 .patches
113 .iter()
114 .map(|p| PatchField::zero_gradient(p.size))
115 .collect();
116 VolScalarField::new(
117 "A",
118 mesh.clone(),
119 Field::new(self.ldu.diag.clone()),
120 boundary,
121 )
122 }
123
124 pub fn h_field(&self, u: &VolVectorField) -> VolVectorField {
129 let mesh = self.mesh.clone();
130 let n = mesh.n_cells;
131 let mut h = vec![Vector3::ZERO; n];
132 for c in 0..n {
133 h[c] = self.source[c];
134 }
135 for f in 0..self.ldu.n_internal_faces {
138 let o = self.ldu.owner[f];
139 let nb = self.ldu.neighbour[f];
140 h[o] = h[o] - u.internal[nb] * self.ldu.upper[f];
141 h[nb] = h[nb] - u.internal[o] * self.ldu.lower[f];
142 }
143 let boundary = mesh
144 .patches
145 .iter()
146 .map(|p| PatchField::zero_gradient_vec(p.size))
147 .collect();
148 VolVectorField::new("H", mesh, Field::new(h), boundary)
149 }
150
151 pub fn solve(
153 &self,
154 name: &str,
155 settings: SolverSettings,
156 ) -> (VolVectorField, SolverPerformance) {
157 let mesh = self.mesh.clone();
158 let n = mesh.n_cells;
159
160 let bx: Vec<f64> = (0..n).map(|c| self.source[c].x).collect();
161 let by: Vec<f64> = (0..n).map(|c| self.source[c].y).collect();
162 let bz: Vec<f64> = (0..n).map(|c| self.source[c].z).collect();
163
164 let mut xx = vec![0.0_f64; n];
165 let mut xy = vec![0.0_f64; n];
166 let mut xz = vec![0.0_f64; n];
167
168 let (ix, rx) = gs(
169 &self.ldu,
170 &bx,
171 &mut xx,
172 settings.tolerance,
173 settings.max_iter,
174 );
175 let (iy, ry) = gs(
176 &self.ldu,
177 &by,
178 &mut xy,
179 settings.tolerance,
180 settings.max_iter,
181 );
182 let (iz, rz) = gs(
183 &self.ldu,
184 &bz,
185 &mut xz,
186 settings.tolerance,
187 settings.max_iter,
188 );
189
190 let internal = Field::from_fn(n, |c| Vector3::new(xx[c], xy[c], xz[c]));
191 let boundary = mesh
192 .patches
193 .iter()
194 .map(|p| PatchField::zero_gradient_vec(p.size))
195 .collect();
196 let u = VolVectorField::new(name, mesh, internal, boundary);
197
198 let n_iters = ix.max(iy).max(iz);
199 let final_res = rx.max(ry).max(rz);
200 let perf = SolverPerformance {
201 n_iterations: n_iters,
202 final_residual: final_res,
203 converged: final_res < settings.tolerance,
204 };
205 (u, perf)
206 }
207
208 pub fn solve_bicgstab(
233 &self,
234 name: &str,
235 options: KrylovOptions,
236 settings: SolverSettings,
237 ) -> (VolVectorField, SolverPerformance) {
238 self.solve_krylov(name, None, KrylovMethod::BiCGStab, options, settings)
239 }
240
241 pub fn solve_gmres(
249 &self,
250 name: &str,
251 options: KrylovOptions,
252 settings: SolverSettings,
253 ) -> (VolVectorField, SolverPerformance) {
254 self.solve_krylov(name, None, KrylovMethod::Gmres, options, settings)
255 }
256
257 pub fn solve_krylov(
265 &self,
266 name: &str,
267 initial: Option<&VolVectorField>,
268 method: KrylovMethod,
269 options: KrylovOptions,
270 settings: SolverSettings,
271 ) -> (VolVectorField, SolverPerformance) {
272 let mesh = self.mesh.clone();
273 let n = mesh.n_cells;
274
275 let b: [Vec<f64>; 3] = [
276 (0..n).map(|c| self.source[c].x).collect(),
277 (0..n).map(|c| self.source[c].y).collect(),
278 (0..n).map(|c| self.source[c].z).collect(),
279 ];
280 let x0: Option<[Vec<f64>; 3]> = initial.map(|f| {
281 [
282 (0..n).map(|c| f.internal[c].x).collect(),
283 (0..n).map(|c| f.internal[c].y).collect(),
284 (0..n).map(|c| f.internal[c].z).collect(),
285 ]
286 });
287
288 let mut sol: [Vec<f64>; 3] = [vec![0.0; n], vec![0.0; n], vec![0.0; n]];
289 let mut n_iters = 0usize;
290 let mut final_res = 0.0_f64;
291 let mut converged = true;
292 for (k, item) in sol.iter_mut().enumerate() {
293 let guess = x0.as_ref().map(|g| g[k].as_slice());
294 let (x, perf) = krylov_solve(&self.ldu, &b[k], guess, method, options, &settings);
295 *item = x;
296 n_iters = n_iters.max(perf.n_iterations);
297 final_res = final_res.max(perf.final_residual);
298 converged &= perf.converged;
299 }
300
301 let internal = Field::from_fn(n, |c| Vector3::new(sol[0][c], sol[1][c], sol[2][c]));
302 let boundary = mesh
303 .patches
304 .iter()
305 .map(|p| PatchField::zero_gradient_vec(p.size))
306 .collect();
307 let u = VolVectorField::new(name, mesh, internal, boundary);
308 let perf = SolverPerformance {
309 n_iterations: n_iters,
310 final_residual: final_res,
311 converged,
312 };
313 (u, perf)
314 }
315}
316
317impl Add for FvVectorMatrix {
320 type Output = Self;
321 fn add(mut self, rhs: Self) -> Self {
322 for (a, b) in self.ldu.diag.iter_mut().zip(&rhs.ldu.diag) {
323 *a += b;
324 }
325 for (a, b) in self.ldu.lower.iter_mut().zip(&rhs.ldu.lower) {
326 *a += b;
327 }
328 for (a, b) in self.ldu.upper.iter_mut().zip(&rhs.ldu.upper) {
329 *a += b;
330 }
331 for c in 0..self.source.len() {
332 self.source[c] = self.source[c] + rhs.source[c];
333 }
334 self
335 }
336}
337
338impl Sub for FvVectorMatrix {
339 type Output = Self;
340 fn sub(mut self, rhs: Self) -> Self {
341 for (a, b) in self.ldu.diag.iter_mut().zip(&rhs.ldu.diag) {
342 *a -= b;
343 }
344 for (a, b) in self.ldu.lower.iter_mut().zip(&rhs.ldu.lower) {
345 *a -= b;
346 }
347 for (a, b) in self.ldu.upper.iter_mut().zip(&rhs.ldu.upper) {
348 *a -= b;
349 }
350 for c in 0..self.source.len() {
351 self.source[c] = self.source[c] - rhs.source[c];
352 }
353 self
354 }
355}
356
357impl Neg for FvVectorMatrix {
358 type Output = Self;
359 fn neg(mut self) -> Self {
360 for x in self.ldu.diag.iter_mut() {
361 *x = -*x;
362 }
363 for x in self.ldu.lower.iter_mut() {
364 *x = -*x;
365 }
366 for x in self.ldu.upper.iter_mut() {
367 *x = -*x;
368 }
369 for c in 0..self.source.len() {
370 self.source[c] = -self.source[c];
371 }
372 self
373 }
374}
375
376impl AddAssign for FvVectorMatrix {
377 fn add_assign(&mut self, rhs: Self) {
378 for (a, b) in self.ldu.diag.iter_mut().zip(&rhs.ldu.diag) {
379 *a += b;
380 }
381 for (a, b) in self.ldu.lower.iter_mut().zip(&rhs.ldu.lower) {
382 *a += b;
383 }
384 for (a, b) in self.ldu.upper.iter_mut().zip(&rhs.ldu.upper) {
385 *a += b;
386 }
387 for c in 0..self.source.len() {
388 self.source[c] = self.source[c] + rhs.source[c];
389 }
390 }
391}
392
393impl SubAssign for FvVectorMatrix {
394 fn sub_assign(&mut self, rhs: Self) {
395 for (a, b) in self.ldu.diag.iter_mut().zip(&rhs.ldu.diag) {
396 *a -= b;
397 }
398 for (a, b) in self.ldu.lower.iter_mut().zip(&rhs.ldu.lower) {
399 *a -= b;
400 }
401 for (a, b) in self.ldu.upper.iter_mut().zip(&rhs.ldu.upper) {
402 *a -= b;
403 }
404 for c in 0..self.source.len() {
405 self.source[c] = self.source[c] - rhs.source[c];
406 }
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413 use crate::mesh::fv_mesh::{BoundaryPatch, FvMeshBuilder, PatchKind};
414
415 fn unit_mesh() -> Arc<FvMesh> {
416 Arc::new(
417 FvMeshBuilder::new()
418 .n_cells(2)
419 .n_internal_faces(1)
420 .owner(vec![0, 1, 0])
421 .neighbour(vec![1])
422 .patches(vec![
423 BoundaryPatch::new("right", 1, 1, PatchKind::Wall),
424 BoundaryPatch::new("left", 2, 1, PatchKind::Wall),
425 ])
426 .cell_volumes(vec![1.0, 1.0])
427 .cell_centres(vec![
428 Vector3::new(0.25, 0.0, 0.0),
429 Vector3::new(0.75, 0.0, 0.0),
430 ])
431 .face_area_vectors(vec![
432 Vector3::new(1.0, 0.0, 0.0),
433 Vector3::new(1.0, 0.0, 0.0),
434 Vector3::new(-1.0, 0.0, 0.0),
435 ])
436 .face_centres(vec![
437 Vector3::new(0.5, 0.0, 0.0),
438 Vector3::new(1.0, 0.0, 0.0),
439 Vector3::new(0.0, 0.0, 0.0),
440 ])
441 .build()
442 .unwrap(),
443 )
444 }
445
446 #[test]
447 fn diagonal_system_solves_vector() {
448 let m = unit_mesh();
449 let mut mat = FvVectorMatrix::new(m.clone());
450 mat.ldu.diag[0] = 2.0;
451 mat.ldu.diag[1] = 3.0;
452 mat.source[0] = Vector3::new(4.0, 6.0, 8.0);
453 mat.source[1] = Vector3::new(6.0, 9.0, 12.0);
454 let (u, perf) = mat.solve("U", SolverSettings::default());
455 assert!(perf.converged, "residual = {}", perf.final_residual);
456 assert!((u.internal[0].x - 2.0).abs() < 1e-8);
457 assert!((u.internal[0].y - 3.0).abs() < 1e-8);
458 assert!((u.internal[1].x - 2.0).abs() < 1e-8);
459 }
460
461 #[test]
462 fn a_field_returns_diagonal() {
463 let m = unit_mesh();
464 let mut mat = FvVectorMatrix::new(m.clone());
465 mat.ldu.diag[0] = 5.0;
466 mat.ldu.diag[1] = 7.0;
467 let a = mat.a_field();
468 assert!((a.internal[0] - 5.0).abs() < 1e-12);
469 assert!((a.internal[1] - 7.0).abs() < 1e-12);
470 }
471}