outram_foam_basic_lib/io/
poly_mesh.rs1use std::path::Path;
49
50use super::dict::{fmt_scalar, tokenize, write_header, Token, BANNER, FOOTER, SEPARATOR};
51use super::IoError;
52use crate::mesh::{BoundaryPatch, FvMesh, FvMeshBuilder, PatchKind};
53use crate::primitives::Vector3;
54
55#[derive(Debug, Clone, PartialEq)]
61pub struct MeshFace {
62 pub verts: Vec<usize>,
64 pub owner: usize,
66 pub neighbour: Option<usize>,
68}
69
70#[derive(Debug, Clone, PartialEq)]
76pub struct PolyMesh {
77 pub points: Vec<Vector3>,
79 pub faces: Vec<MeshFace>,
81 pub n_internal_faces: usize,
83 pub n_cells: usize,
85 pub patches: Vec<BoundaryPatch>,
87}
88
89impl PolyMesh {
90 pub fn n_points(&self) -> usize {
92 self.points.len()
93 }
94 pub fn n_faces(&self) -> usize {
96 self.faces.len()
97 }
98 pub fn n_boundary_faces(&self) -> usize {
100 self.faces.len() - self.n_internal_faces
101 }
102
103 pub fn total_volume(&self) -> f64 {
105 self.cell_geometry().0.iter().sum()
106 }
107
108 fn cell_geometry(&self) -> (Vec<f64>, Vec<Vector3>) {
111 let mut face_ctr = Vec::with_capacity(self.faces.len());
112 let mut face_sf = Vec::with_capacity(self.faces.len());
113 for f in &self.faces {
114 let (c, sf) = face_centre_and_area(&self.points, &f.verts);
115 face_ctr.push(c);
116 face_sf.push(sf);
117 }
118
119 let mut c_est = vec![Vector3::ZERO; self.n_cells];
121 let mut n_cell_faces = vec![0.0f64; self.n_cells];
122 for (fi, f) in self.faces.iter().enumerate() {
123 c_est[f.owner] += face_ctr[fi];
124 n_cell_faces[f.owner] += 1.0;
125 if let Some(nb) = f.neighbour {
126 c_est[nb] += face_ctr[fi];
127 n_cell_faces[nb] += 1.0;
128 }
129 }
130 for c in 0..self.n_cells {
131 if n_cell_faces[c] > 0.0 {
132 c_est[c] /= n_cell_faces[c];
133 }
134 }
135
136 let mut vol = vec![0.0f64; self.n_cells];
138 let mut ctr = vec![Vector3::ZERO; self.n_cells];
139 for (fi, f) in self.faces.iter().enumerate() {
140 let cf = face_ctr[fi];
141 {
142 let cell = f.owner;
143 let sf = face_sf[fi];
144 let pyr3 = sf.dot(cf - c_est[cell]);
145 let pyr_ctr = cf * 0.75 + c_est[cell] * 0.25;
146 vol[cell] += pyr3;
147 ctr[cell] += pyr_ctr * pyr3;
148 }
149 if let Some(cell) = f.neighbour {
150 let sf = face_sf[fi] * -1.0;
151 let pyr3 = sf.dot(cf - c_est[cell]);
152 let pyr_ctr = cf * 0.75 + c_est[cell] * 0.25;
153 vol[cell] += pyr3;
154 ctr[cell] += pyr_ctr * pyr3;
155 }
156 }
157 for c in 0..self.n_cells {
158 if vol[c].abs() > f64::EPSILON {
159 ctr[c] /= vol[c];
160 } else {
161 ctr[c] = c_est[c];
162 }
163 vol[c] /= 3.0;
164 }
165 (vol, ctr)
166 }
167
168 pub fn to_fv_mesh(&self) -> Result<FvMesh, IoError> {
175 let (cell_volumes, cell_centres) = self.cell_geometry();
176
177 let mut owner = Vec::with_capacity(self.faces.len());
178 let mut neighbour = Vec::with_capacity(self.n_internal_faces);
179 let mut face_area_vectors = Vec::with_capacity(self.faces.len());
180 let mut face_centres = Vec::with_capacity(self.faces.len());
181 for f in &self.faces {
182 let (c, sf) = face_centre_and_area(&self.points, &f.verts);
183 owner.push(f.owner);
184 if let Some(nb) = f.neighbour {
185 neighbour.push(nb);
186 }
187 face_area_vectors.push(sf);
188 face_centres.push(c);
189 }
190
191 FvMeshBuilder::new()
192 .n_cells(self.n_cells)
193 .n_internal_faces(self.n_internal_faces)
194 .owner(owner)
195 .neighbour(neighbour)
196 .patches(self.patches.clone())
197 .cell_volumes(cell_volumes)
198 .cell_centres(cell_centres)
199 .face_area_vectors(face_area_vectors)
200 .face_centres(face_centres)
201 .build()
202 .map_err(|e| IoError::Mesh(e.to_string()))
203 }
204
205 pub fn read(dir: impl AsRef<Path>) -> Result<Self, IoError> {
211 let dir = dir.as_ref();
212 let points = read_points(&dir.join("points"))?;
213 let face_loops = read_faces(&dir.join("faces"))?;
214 let owner = read_labels(&dir.join("owner"))?;
215 let neighbour = read_labels(&dir.join("neighbour"))?;
216 let raw_patches = read_boundary(&dir.join("boundary"))?;
217
218 if owner.len() != face_loops.len() {
219 return Err(IoError::parse(
220 "polyMesh",
221 format!(
222 "owner has {} entries but faces has {}",
223 owner.len(),
224 face_loops.len()
225 ),
226 ));
227 }
228 let n_internal_faces = neighbour.len();
229 let mut faces = Vec::with_capacity(face_loops.len());
230 for (fi, verts) in face_loops.into_iter().enumerate() {
231 let nb = if fi < n_internal_faces {
232 Some(neighbour[fi])
233 } else {
234 None
235 };
236 faces.push(MeshFace {
237 verts,
238 owner: owner[fi],
239 neighbour: nb,
240 });
241 }
242
243 let mut n_cells = 0usize;
245 for f in &faces {
246 n_cells = n_cells.max(f.owner + 1);
247 if let Some(nb) = f.neighbour {
248 n_cells = n_cells.max(nb + 1);
249 }
250 }
251
252 let patches = raw_patches
253 .into_iter()
254 .map(|p| BoundaryPatch::new(p.name, p.start_face, p.n_faces, kind_from_str(&p.kind)))
255 .collect();
256
257 Ok(Self {
258 points,
259 faces,
260 n_internal_faces,
261 n_cells,
262 patches,
263 })
264 }
265
266 pub fn write(&self, dir: impl AsRef<Path>) -> Result<(), IoError> {
270 let dir = dir.as_ref();
271 std::fs::create_dir_all(dir).map_err(|e| IoError::io(dir.display().to_string(), e))?;
272 write_file(&dir.join("points"), &self.points_string())?;
273 write_file(&dir.join("faces"), &self.faces_string())?;
274 write_file(&dir.join("owner"), &self.owner_string())?;
275 write_file(&dir.join("neighbour"), &self.neighbour_string())?;
276 write_file(&dir.join("boundary"), &self.boundary_string())?;
277 Ok(())
278 }
279
280 fn points_string(&self) -> String {
281 let mut s = header_str("vectorField", "points");
282 s.push_str(&format!("{}\n(\n", self.points.len()));
283 for p in &self.points {
284 s.push_str(&format!(
285 "({} {} {})\n",
286 fmt_scalar(p.x),
287 fmt_scalar(p.y),
288 fmt_scalar(p.z)
289 ));
290 }
291 s.push_str(")\n\n");
292 s.push_str(FOOTER);
293 s
294 }
295
296 fn faces_string(&self) -> String {
297 let mut s = header_str("faceList", "faces");
298 s.push_str(&format!("{}\n(\n", self.faces.len()));
299 for f in &self.faces {
300 s.push_str(&format!("{}(", f.verts.len()));
301 for (i, v) in f.verts.iter().enumerate() {
302 if i > 0 {
303 s.push(' ');
304 }
305 s.push_str(&v.to_string());
306 }
307 s.push_str(")\n");
308 }
309 s.push_str(")\n\n");
310 s.push_str(FOOTER);
311 s
312 }
313
314 fn owner_string(&self) -> String {
315 let mut s = header_str("labelList", "owner");
316 s.push_str(&format!("{}\n(\n", self.faces.len()));
317 for f in &self.faces {
318 s.push_str(&f.owner.to_string());
319 s.push('\n');
320 }
321 s.push_str(")\n\n");
322 s.push_str(FOOTER);
323 s
324 }
325
326 fn neighbour_string(&self) -> String {
327 let mut s = header_str("labelList", "neighbour");
328 s.push_str(&format!("{}\n(\n", self.n_internal_faces));
329 for f in self.faces.iter().take(self.n_internal_faces) {
330 s.push_str(&f.neighbour.unwrap_or(0).to_string());
331 s.push('\n');
332 }
333 s.push_str(")\n\n");
334 s.push_str(FOOTER);
335 s
336 }
337
338 fn boundary_string(&self) -> String {
339 let mut s = header_str("polyBoundaryMesh", "boundary");
340 s.push_str(&format!("{}\n(\n", self.patches.len()));
341 for p in &self.patches {
342 s.push_str(&format!(" {}\n {{\n", p.name));
343 s.push_str(&format!(
344 " type {};\n",
345 kind_to_str(p.kind)
346 ));
347 s.push_str(&format!(" nFaces {};\n", p.size));
348 s.push_str(&format!(" startFace {};\n", p.start));
349 s.push_str(" }\n");
350 }
351 s.push_str(")\n\n");
352 s.push_str(FOOTER);
353 s
354 }
355}
356
357fn face_centre_and_area(points: &[Vector3], verts: &[usize]) -> (Vector3, Vector3) {
362 let n = verts.len();
363 debug_assert!(n >= 3);
364 if n == 3 {
365 let a = points[verts[0]];
366 let b = points[verts[1]];
367 let c = points[verts[2]];
368 let centre = (a + b + c) / 3.0;
369 let area = (b - a).cross(c - a) * 0.5;
370 return (centre, area);
371 }
372 let mut c_est = Vector3::ZERO;
373 for &v in verts {
374 c_est += points[v];
375 }
376 c_est /= n as f64;
377
378 let mut sum_a = 0.0;
379 let mut sum_n = Vector3::ZERO;
380 let mut sum_ac = Vector3::ZERO;
381 for i in 0..n {
382 let p1 = points[verts[i]];
383 let p2 = points[verts[(i + 1) % n]];
384 let mid = (p1 + p2 + c_est) / 3.0;
385 let n_tri = (p2 - p1).cross(c_est - p1);
386 let a_tri = n_tri.mag();
387 sum_a += a_tri;
388 sum_n += n_tri;
389 sum_ac += mid * a_tri;
390 }
391 let centre = if sum_a > f64::EPSILON {
392 sum_ac / sum_a
393 } else {
394 c_est
395 };
396 (centre, sum_n * 0.5)
397}
398
399fn kind_from_str(s: &str) -> PatchKind {
402 match s {
403 "wall" => PatchKind::Wall,
404 "symmetry" | "symmetryPlane" => PatchKind::Symmetry,
405 "empty" => PatchKind::Empty,
406 "wedge" => PatchKind::Wedge,
407 "cyclic" => PatchKind::Cyclic,
408 "cyclicAMI" => PatchKind::CyclicAmi,
409 "processor" => PatchKind::Processor,
410 _ => PatchKind::Patch,
411 }
412}
413
414fn kind_to_str(k: PatchKind) -> &'static str {
415 match k {
416 PatchKind::Patch => "patch",
417 PatchKind::Wall => "wall",
418 PatchKind::Symmetry => "symmetry",
419 PatchKind::Empty => "empty",
420 PatchKind::Wedge => "wedge",
421 PatchKind::Cyclic => "cyclic",
422 PatchKind::CyclicAmi => "cyclicAMI",
423 PatchKind::Processor => "processor",
424 }
425}
426
427struct ListCursor {
431 tokens: Vec<Token>,
432 pos: usize,
433 context: String,
434}
435
436impl ListCursor {
437 fn new(text: &str, context: impl Into<String>) -> Self {
438 let mut c = Self {
439 tokens: tokenize(text),
440 pos: 0,
441 context: context.into(),
442 };
443 c.skip_header();
444 c
445 }
446
447 fn err(&self, m: impl Into<String>) -> IoError {
448 IoError::parse(self.context.clone(), m.into())
449 }
450
451 fn peek(&self) -> Option<&str> {
452 self.tokens.get(self.pos).map(|t| t.text.as_str())
453 }
454
455 fn next(&mut self) -> Option<String> {
456 let t = self.tokens.get(self.pos).map(|t| t.text.clone());
457 if t.is_some() {
458 self.pos += 1;
459 }
460 t
461 }
462
463 fn expect(&mut self, tok: &str) -> Result<(), IoError> {
464 match self.next() {
465 Some(t) if t == tok => Ok(()),
466 other => Err(self.err(format!("expected `{tok}`, found {other:?}"))),
467 }
468 }
469
470 fn skip_header(&mut self) {
472 if self.peek() == Some("FoamFile") {
473 self.next(); let mut depth = 0usize;
476 while let Some(t) = self.next() {
477 match t.as_str() {
478 "{" => depth += 1,
479 "}" => {
480 depth -= 1;
481 if depth == 0 {
482 break;
483 }
484 }
485 _ => {}
486 }
487 }
488 }
489 }
490
491 fn parse_usize(&mut self) -> Result<usize, IoError> {
492 let t = self
493 .next()
494 .ok_or_else(|| self.err("expected an integer, found end of input"))?;
495 t.parse::<usize>()
496 .map_err(|_| self.err(format!("expected an integer, found `{t}`")))
497 }
498
499 fn parse_f64(&mut self) -> Result<f64, IoError> {
500 let t = self
501 .next()
502 .ok_or_else(|| self.err("expected a number, found end of input"))?;
503 t.parse::<f64>()
504 .map_err(|_| self.err(format!("expected a number, found `{t}`")))
505 }
506}
507
508fn read_points(path: &Path) -> Result<Vec<Vector3>, IoError> {
509 let text = read_to_string(path)?;
510 let mut c = ListCursor::new(&text, path.display().to_string());
511 let count = c.parse_usize()?;
512 c.expect("(")?;
513 let mut pts = Vec::with_capacity(count);
514 for _ in 0..count {
515 c.expect("(")?;
516 let x = c.parse_f64()?;
517 let y = c.parse_f64()?;
518 let z = c.parse_f64()?;
519 c.expect(")")?;
520 pts.push(Vector3::new(x, y, z));
521 }
522 c.expect(")")?;
523 Ok(pts)
524}
525
526fn read_faces(path: &Path) -> Result<Vec<Vec<usize>>, IoError> {
527 let text = read_to_string(path)?;
528 let mut c = ListCursor::new(&text, path.display().to_string());
529 let count = c.parse_usize()?;
530 c.expect("(")?;
531 let mut faces = Vec::with_capacity(count);
532 for _ in 0..count {
533 let nverts = c.parse_usize()?;
534 c.expect("(")?;
535 let mut verts = Vec::with_capacity(nverts);
536 for _ in 0..nverts {
537 verts.push(c.parse_usize()?);
538 }
539 c.expect(")")?;
540 faces.push(verts);
541 }
542 c.expect(")")?;
543 Ok(faces)
544}
545
546fn read_labels(path: &Path) -> Result<Vec<usize>, IoError> {
547 let text = read_to_string(path)?;
548 let mut c = ListCursor::new(&text, path.display().to_string());
549 let count = c.parse_usize()?;
550 c.expect("(")?;
551 let mut out = Vec::with_capacity(count);
552 for _ in 0..count {
553 out.push(c.parse_usize()?);
554 }
555 c.expect(")")?;
556 Ok(out)
557}
558
559struct RawPatch {
560 name: String,
561 kind: String,
562 n_faces: usize,
563 start_face: usize,
564}
565
566fn read_boundary(path: &Path) -> Result<Vec<RawPatch>, IoError> {
567 let text = read_to_string(path)?;
568 let mut c = ListCursor::new(&text, path.display().to_string());
569 let count = c.parse_usize()?;
570 c.expect("(")?;
571 let mut patches = Vec::with_capacity(count);
572 for _ in 0..count {
573 let name = c
574 .next()
575 .ok_or_else(|| c.err("expected a patch name"))?
576 .to_string();
577 c.expect("{")?;
578 let mut kind = String::from("patch");
579 let mut n_faces = 0usize;
580 let mut start_face = 0usize;
581 while c.peek() != Some("}") {
584 let key = c
585 .next()
586 .ok_or_else(|| c.err("unexpected end of input in patch entry"))?
587 .to_string();
588 match key.as_str() {
589 "type" => {
590 kind = c
591 .next()
592 .ok_or_else(|| c.err("expected a patch type"))?
593 .to_string();
594 c.expect(";")?;
595 }
596 "nFaces" => {
597 n_faces = c.parse_usize()?;
598 c.expect(";")?;
599 }
600 "startFace" => {
601 start_face = c.parse_usize()?;
602 c.expect(";")?;
603 }
604 _ => {
605 let mut depth = 0usize;
607 loop {
608 match c.next().as_deref() {
609 Some("(") => depth += 1,
610 Some(")") => depth = depth.saturating_sub(1),
611 Some(";") if depth == 0 => break,
612 Some(_) => {}
613 None => return Err(c.err("unterminated patch entry")),
614 }
615 }
616 }
617 }
618 }
619 c.expect("}")?;
620 patches.push(RawPatch {
621 name,
622 kind,
623 n_faces,
624 start_face,
625 });
626 }
627 c.expect(")")?;
628 Ok(patches)
629}
630
631fn read_to_string(path: &Path) -> Result<String, IoError> {
634 std::fs::read_to_string(path).map_err(|e| IoError::io(path.display().to_string(), e))
635}
636
637fn write_file(path: &Path, contents: &str) -> Result<(), IoError> {
638 std::fs::write(path, contents).map_err(|e| IoError::io(path.display().to_string(), e))
639}
640
641fn header_str(class: &str, object: &str) -> String {
643 let mut s = String::new();
644 s.push_str(BANNER);
645 let header =
646 super::dict::FoamHeader::standard_with_location(class, "constant/polyMesh", object);
647 write_header(&mut s, &header);
648 s.push_str(SEPARATOR);
649 s.push_str("\n\n");
650 s
651}