outram_foam_basic_lib/ldu_matrix/
fv_vector_matrix.rs1use 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 crate::fields::boundary::bc::PatchField;
29use crate::fields::field::Field;
30use crate::fields::vol_field::{VolScalarField, VolVectorField};
31use crate::mesh::fv_mesh::FvMesh;
32use crate::primitives::Vector3;
33
34#[derive(Debug, Clone)]
43pub struct FvVectorMatrix {
44 pub mesh: Arc<FvMesh>,
46 pub ldu: LduMatrix,
48 pub source: Field<Vector3>,
50}
51
52impl FvVectorMatrix {
53 pub fn new(mesh: Arc<FvMesh>) -> Self {
56 let n_cells = mesh.n_cells;
57 let owner = mesh.owner[..mesh.n_internal_faces].to_vec();
58 let neighbour = mesh.neighbour.clone();
59 Self {
60 ldu: LduMatrix::new(n_cells, owner, neighbour),
61 source: Field::from_fn(n_cells, |_| Vector3::ZERO),
62 mesh,
63 }
64 }
65
66 pub fn add_to_diag(&mut self, coeff: &Field<f64>) {
68 for c in 0..self.mesh.n_cells {
69 self.ldu.diag[c] += coeff[c];
70 }
71 }
72
73 pub fn add_to_source(&mut self, term: &Field<Vector3>) {
75 for c in 0..self.mesh.n_cells {
76 self.source[c] = self.source[c] + term[c];
77 }
78 }
79
80 pub fn set_reference(&mut self, cell: usize, value: Vector3) {
82 self.ldu.diag[cell] += 1e30;
83 self.source[cell] = self.source[cell] + value * 1e30;
84 }
85
86 pub fn a_field(&self) -> VolScalarField {
90 let mesh = self.mesh.clone();
91 let boundary = mesh
92 .patches
93 .iter()
94 .map(|p| PatchField::zero_gradient(p.size))
95 .collect();
96 VolScalarField::new(
97 "A",
98 mesh.clone(),
99 Field::new(self.ldu.diag.clone()),
100 boundary,
101 )
102 }
103
104 pub fn h_field(&self, u: &VolVectorField) -> VolVectorField {
109 let mesh = self.mesh.clone();
110 let n = mesh.n_cells;
111 let mut h = vec![Vector3::ZERO; n];
112 for c in 0..n {
113 h[c] = self.source[c];
114 }
115 for f in 0..mesh.n_internal_faces {
116 let o = self.ldu.owner[f];
117 let nb = self.ldu.neighbour[f];
118 h[o] = h[o] - u.internal[nb] * self.ldu.upper[f];
119 h[nb] = h[nb] - u.internal[o] * self.ldu.lower[f];
120 }
121 let boundary = mesh
122 .patches
123 .iter()
124 .map(|p| PatchField::zero_gradient_vec(p.size))
125 .collect();
126 VolVectorField::new("H", mesh, Field::new(h), boundary)
127 }
128
129 pub fn solve(
131 &self,
132 name: &str,
133 settings: SolverSettings,
134 ) -> (VolVectorField, SolverPerformance) {
135 let mesh = self.mesh.clone();
136 let n = mesh.n_cells;
137
138 let bx: Vec<f64> = (0..n).map(|c| self.source[c].x).collect();
139 let by: Vec<f64> = (0..n).map(|c| self.source[c].y).collect();
140 let bz: Vec<f64> = (0..n).map(|c| self.source[c].z).collect();
141
142 let mut xx = vec![0.0_f64; n];
143 let mut xy = vec![0.0_f64; n];
144 let mut xz = vec![0.0_f64; n];
145
146 let (ix, rx) = gs(
147 &self.ldu,
148 &bx,
149 &mut xx,
150 settings.tolerance,
151 settings.max_iter,
152 );
153 let (iy, ry) = gs(
154 &self.ldu,
155 &by,
156 &mut xy,
157 settings.tolerance,
158 settings.max_iter,
159 );
160 let (iz, rz) = gs(
161 &self.ldu,
162 &bz,
163 &mut xz,
164 settings.tolerance,
165 settings.max_iter,
166 );
167
168 let internal = Field::from_fn(n, |c| Vector3::new(xx[c], xy[c], xz[c]));
169 let boundary = mesh
170 .patches
171 .iter()
172 .map(|p| PatchField::zero_gradient_vec(p.size))
173 .collect();
174 let u = VolVectorField::new(name, mesh, internal, boundary);
175
176 let n_iters = ix.max(iy).max(iz);
177 let final_res = rx.max(ry).max(rz);
178 let perf = SolverPerformance {
179 n_iterations: n_iters,
180 final_residual: final_res,
181 converged: final_res < settings.tolerance,
182 };
183 (u, perf)
184 }
185}
186
187impl Add for FvVectorMatrix {
190 type Output = Self;
191 fn add(mut self, rhs: Self) -> Self {
192 for (a, b) in self.ldu.diag.iter_mut().zip(&rhs.ldu.diag) {
193 *a += b;
194 }
195 for (a, b) in self.ldu.lower.iter_mut().zip(&rhs.ldu.lower) {
196 *a += b;
197 }
198 for (a, b) in self.ldu.upper.iter_mut().zip(&rhs.ldu.upper) {
199 *a += b;
200 }
201 for c in 0..self.source.len() {
202 self.source[c] = self.source[c] + rhs.source[c];
203 }
204 self
205 }
206}
207
208impl Sub for FvVectorMatrix {
209 type Output = Self;
210 fn sub(mut self, rhs: Self) -> Self {
211 for (a, b) in self.ldu.diag.iter_mut().zip(&rhs.ldu.diag) {
212 *a -= b;
213 }
214 for (a, b) in self.ldu.lower.iter_mut().zip(&rhs.ldu.lower) {
215 *a -= b;
216 }
217 for (a, b) in self.ldu.upper.iter_mut().zip(&rhs.ldu.upper) {
218 *a -= b;
219 }
220 for c in 0..self.source.len() {
221 self.source[c] = self.source[c] - rhs.source[c];
222 }
223 self
224 }
225}
226
227impl Neg for FvVectorMatrix {
228 type Output = Self;
229 fn neg(mut self) -> Self {
230 for x in self.ldu.diag.iter_mut() {
231 *x = -*x;
232 }
233 for x in self.ldu.lower.iter_mut() {
234 *x = -*x;
235 }
236 for x in self.ldu.upper.iter_mut() {
237 *x = -*x;
238 }
239 for c in 0..self.source.len() {
240 self.source[c] = -self.source[c];
241 }
242 self
243 }
244}
245
246impl AddAssign for FvVectorMatrix {
247 fn add_assign(&mut self, rhs: Self) {
248 for (a, b) in self.ldu.diag.iter_mut().zip(&rhs.ldu.diag) {
249 *a += b;
250 }
251 for (a, b) in self.ldu.lower.iter_mut().zip(&rhs.ldu.lower) {
252 *a += b;
253 }
254 for (a, b) in self.ldu.upper.iter_mut().zip(&rhs.ldu.upper) {
255 *a += b;
256 }
257 for c in 0..self.source.len() {
258 self.source[c] = self.source[c] + rhs.source[c];
259 }
260 }
261}
262
263impl SubAssign for FvVectorMatrix {
264 fn sub_assign(&mut self, rhs: Self) {
265 for (a, b) in self.ldu.diag.iter_mut().zip(&rhs.ldu.diag) {
266 *a -= b;
267 }
268 for (a, b) in self.ldu.lower.iter_mut().zip(&rhs.ldu.lower) {
269 *a -= b;
270 }
271 for (a, b) in self.ldu.upper.iter_mut().zip(&rhs.ldu.upper) {
272 *a -= b;
273 }
274 for c in 0..self.source.len() {
275 self.source[c] = self.source[c] - rhs.source[c];
276 }
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::mesh::fv_mesh::{BoundaryPatch, FvMeshBuilder, PatchKind};
284
285 fn unit_mesh() -> Arc<FvMesh> {
286 Arc::new(
287 FvMeshBuilder::new()
288 .n_cells(2)
289 .n_internal_faces(1)
290 .owner(vec![0, 1, 0])
291 .neighbour(vec![1])
292 .patches(vec![
293 BoundaryPatch::new("right", 1, 1, PatchKind::Wall),
294 BoundaryPatch::new("left", 2, 1, PatchKind::Wall),
295 ])
296 .cell_volumes(vec![1.0, 1.0])
297 .cell_centres(vec![
298 Vector3::new(0.25, 0.0, 0.0),
299 Vector3::new(0.75, 0.0, 0.0),
300 ])
301 .face_area_vectors(vec![
302 Vector3::new(1.0, 0.0, 0.0),
303 Vector3::new(1.0, 0.0, 0.0),
304 Vector3::new(-1.0, 0.0, 0.0),
305 ])
306 .face_centres(vec![
307 Vector3::new(0.5, 0.0, 0.0),
308 Vector3::new(1.0, 0.0, 0.0),
309 Vector3::new(0.0, 0.0, 0.0),
310 ])
311 .build()
312 .unwrap(),
313 )
314 }
315
316 #[test]
317 fn diagonal_system_solves_vector() {
318 let m = unit_mesh();
319 let mut mat = FvVectorMatrix::new(m.clone());
320 mat.ldu.diag[0] = 2.0;
321 mat.ldu.diag[1] = 3.0;
322 mat.source[0] = Vector3::new(4.0, 6.0, 8.0);
323 mat.source[1] = Vector3::new(6.0, 9.0, 12.0);
324 let (u, perf) = mat.solve("U", SolverSettings::default());
325 assert!(perf.converged, "residual = {}", perf.final_residual);
326 assert!((u.internal[0].x - 2.0).abs() < 1e-8);
327 assert!((u.internal[0].y - 3.0).abs() < 1e-8);
328 assert!((u.internal[1].x - 2.0).abs() < 1e-8);
329 }
330
331 #[test]
332 fn a_field_returns_diagonal() {
333 let m = unit_mesh();
334 let mut mat = FvVectorMatrix::new(m.clone());
335 mat.ldu.diag[0] = 5.0;
336 mat.ldu.diag[1] = 7.0;
337 let a = mat.a_field();
338 assert!((a.internal[0] - 5.0).abs() < 1e-12);
339 assert!((a.internal[1] - 7.0).abs() < 1e-12);
340 }
341}