Skip to main content

mesh_sieve/mesh_generation/
mod.rs

1//! Structured mesh generators with boundary labeling and periodic equivalence.
2
3use crate::data::atlas::Atlas;
4use crate::data::coordinates::Coordinates;
5use crate::data::mixed_section::MixedSectionStore;
6use crate::data::section::Section;
7use crate::data::storage::VecStorage;
8use crate::io::MeshData;
9use crate::mesh_error::MeshSieveError;
10use crate::topology::cell_type::CellType;
11use crate::topology::labels::LabelSet;
12use crate::topology::periodic::PointEquivalence;
13use crate::topology::point::PointId;
14use crate::topology::sieve::{InMemorySieve, MutableSieve, Sieve};
15use std::collections::BTreeMap;
16
17/// Boundary label name for the minimum-x side.
18pub const BOUNDARY_X_MIN: &str = "boundary_x_min";
19/// Boundary label name for the maximum-x side.
20pub const BOUNDARY_X_MAX: &str = "boundary_x_max";
21/// Boundary label name for the minimum-y side.
22pub const BOUNDARY_Y_MIN: &str = "boundary_y_min";
23/// Boundary label name for the maximum-y side.
24pub const BOUNDARY_Y_MAX: &str = "boundary_y_max";
25/// Boundary label name for the minimum-z side.
26pub const BOUNDARY_Z_MIN: &str = "boundary_z_min";
27/// Boundary label name for the maximum-z side.
28pub const BOUNDARY_Z_MAX: &str = "boundary_z_max";
29
30type MeshDataType =
31    MeshData<InMemorySieve<PointId, ()>, f64, VecStorage<f64>, VecStorage<CellType>>;
32
33/// Optional periodic identification for structured meshes.
34#[derive(Clone, Copy, Debug, Default)]
35pub struct Periodicity {
36    pub x: bool,
37    pub y: bool,
38    pub z: bool,
39}
40
41impl Periodicity {
42    /// No periodic directions.
43    pub fn none() -> Self {
44        Self::default()
45    }
46}
47
48/// Optional configuration for mesh generation.
49#[derive(Clone, Copy, Debug, Default)]
50pub struct MeshGenerationOptions {
51    pub periodic: Periodicity,
52}
53
54/// Output from a mesh generator with optional periodic equivalence.
55#[derive(Debug)]
56pub struct GeneratedMesh {
57    pub mesh: MeshDataType,
58    pub periodic: Option<PointEquivalence>,
59}
60
61fn invalid_geometry(message: impl Into<String>) -> MeshSieveError {
62    MeshSieveError::InvalidGeometry(message.into())
63}
64
65fn build_mesh(
66    dimension: usize,
67    vertex_coords: &[Vec<f64>],
68    cells: &[Vec<usize>],
69    cell_type: CellType,
70) -> Result<(MeshDataType, Vec<PointId>), MeshSieveError> {
71    if dimension == 0 {
72        return Err(invalid_geometry("dimension must be non-zero"));
73    }
74    for (idx, coord) in vertex_coords.iter().enumerate() {
75        if coord.len() != dimension {
76            return Err(invalid_geometry(format!(
77                "vertex {idx} has dimension {}, expected {dimension}",
78                coord.len()
79            )));
80        }
81    }
82
83    let mut sieve = InMemorySieve::<PointId, ()>::default();
84    let mut next_id = 1u64;
85
86    let mut vertex_points = Vec::with_capacity(vertex_coords.len());
87    for _ in 0..vertex_coords.len() {
88        let pid = PointId::new(next_id)?;
89        next_id += 1;
90        MutableSieve::add_point(&mut sieve, pid);
91        vertex_points.push(pid);
92    }
93
94    let mut cell_points = Vec::with_capacity(cells.len());
95    for _ in 0..cells.len() {
96        let pid = PointId::new(next_id)?;
97        next_id += 1;
98        MutableSieve::add_point(&mut sieve, pid);
99        cell_points.push(pid);
100    }
101
102    for (cell_idx, vertices) in cells.iter().enumerate() {
103        let cell_point = cell_points[cell_idx];
104        for &vidx in vertices {
105            let vpoint = *vertex_points.get(vidx).ok_or_else(|| {
106                invalid_geometry(format!("cell {cell_idx} references missing vertex {vidx}"))
107            })?;
108            sieve.add_arrow(cell_point, vpoint, ())?;
109        }
110    }
111    sieve.sort_adjacency();
112
113    let mut coord_atlas = Atlas::default();
114    for &p in &vertex_points {
115        coord_atlas.try_insert(p, dimension)?;
116    }
117    let mut coords =
118        Coordinates::<f64, VecStorage<f64>>::try_new(dimension, dimension, coord_atlas)?;
119    for (p, coord) in vertex_points.iter().zip(vertex_coords.iter()) {
120        coords.section_mut().try_set(*p, coord)?;
121    }
122
123    let mut cell_atlas = Atlas::default();
124    for &p in vertex_points.iter().chain(cell_points.iter()) {
125        cell_atlas.try_insert(p, 1)?;
126    }
127    let mut cell_types = Section::<CellType, VecStorage<CellType>>::new(cell_atlas);
128    for &p in &vertex_points {
129        cell_types.try_set(p, &[CellType::Vertex])?;
130    }
131    for &p in &cell_points {
132        cell_types.try_set(p, &[cell_type])?;
133    }
134
135    Ok((
136        MeshDataType {
137            sieve,
138            coordinates: Some(coords),
139            sections: BTreeMap::new(),
140            mixed_sections: MixedSectionStore::default(),
141            labels: None,
142            cell_types: Some(cell_types),
143            discretization: None,
144        },
145        vertex_points,
146    ))
147}
148
149/// Generate a 1D interval mesh with `n` segments over `[min, max]`.
150pub fn interval_mesh(
151    n: usize,
152    min: f64,
153    max: f64,
154    options: MeshGenerationOptions,
155) -> Result<GeneratedMesh, MeshSieveError> {
156    if n == 0 {
157        return Err(invalid_geometry("n must be positive"));
158    }
159
160    let dx = (max - min) / n as f64;
161    let mut vertices = Vec::with_capacity(n + 1);
162    for i in 0..=n {
163        vertices.push(vec![min + dx * i as f64]);
164    }
165
166    let mut cells = Vec::with_capacity(n);
167    for i in 0..n {
168        cells.push(vec![i, i + 1]);
169    }
170
171    let (mut mesh, vertex_points) = build_mesh(1, &vertices, &cells, CellType::Segment)?;
172
173    let mut labels = LabelSet::new();
174    if let Some(&first) = vertex_points.first() {
175        labels.set_label(first, BOUNDARY_X_MIN, 1);
176    }
177    if let Some(&last) = vertex_points.last() {
178        labels.set_label(last, BOUNDARY_X_MAX, 1);
179    }
180    mesh.labels = Some(labels);
181
182    let periodic = if options.periodic.x {
183        let mut eq = PointEquivalence::new();
184        if let (Some(&first), Some(&last)) = (vertex_points.first(), vertex_points.last()) {
185            eq.add_equivalence(first, last);
186        }
187        Some(eq)
188    } else {
189        None
190    };
191
192    Ok(GeneratedMesh { mesh, periodic })
193}
194
195/// Generate a structured quadrilateral mesh over `[min, max]` with `nx`×`ny` cells.
196pub fn quad_mesh(
197    nx: usize,
198    ny: usize,
199    min: [f64; 2],
200    max: [f64; 2],
201    options: MeshGenerationOptions,
202) -> Result<GeneratedMesh, MeshSieveError> {
203    if nx == 0 || ny == 0 {
204        return Err(invalid_geometry("nx and ny must be positive"));
205    }
206
207    let dx = (max[0] - min[0]) / nx as f64;
208    let dy = (max[1] - min[1]) / ny as f64;
209    let mut vertices = Vec::with_capacity((nx + 1) * (ny + 1));
210    for j in 0..=ny {
211        let y = min[1] + dy * j as f64;
212        for i in 0..=nx {
213            let x = min[0] + dx * i as f64;
214            vertices.push(vec![x, y]);
215        }
216    }
217
218    let mut cells = Vec::with_capacity(nx * ny);
219    let row_stride = nx + 1;
220    for j in 0..ny {
221        for i in 0..nx {
222            let v0 = j * row_stride + i;
223            let v1 = v0 + 1;
224            let v3 = v0 + row_stride;
225            let v2 = v3 + 1;
226            cells.push(vec![v0, v1, v2, v3]);
227        }
228    }
229
230    let (mut mesh, vertex_points) = build_mesh(2, &vertices, &cells, CellType::Quadrilateral)?;
231
232    let mut labels = LabelSet::new();
233    for j in 0..=ny {
234        for i in 0..=nx {
235            let idx = j * (nx + 1) + i;
236            let point = vertex_points[idx];
237            if i == 0 {
238                labels.set_label(point, BOUNDARY_X_MIN, 1);
239            }
240            if i == nx {
241                labels.set_label(point, BOUNDARY_X_MAX, 1);
242            }
243            if j == 0 {
244                labels.set_label(point, BOUNDARY_Y_MIN, 1);
245            }
246            if j == ny {
247                labels.set_label(point, BOUNDARY_Y_MAX, 1);
248            }
249        }
250    }
251    mesh.labels = Some(labels);
252
253    let periodic = if options.periodic.x || options.periodic.y {
254        let mut eq = PointEquivalence::new();
255        if options.periodic.x {
256            for j in 0..=ny {
257                let left_idx = j * (nx + 1);
258                let right_idx = left_idx + nx;
259                eq.add_equivalence(vertex_points[left_idx], vertex_points[right_idx]);
260            }
261        }
262        if options.periodic.y {
263            for i in 0..=nx {
264                let bottom_idx = i;
265                let top_idx = ny * (nx + 1) + i;
266                eq.add_equivalence(vertex_points[bottom_idx], vertex_points[top_idx]);
267            }
268        }
269        Some(eq)
270    } else {
271        None
272    };
273
274    Ok(GeneratedMesh { mesh, periodic })
275}
276
277/// Generate a structured hexahedral mesh over `[min, max]` with `nx`×`ny`×`nz` cells.
278pub fn hex_mesh(
279    nx: usize,
280    ny: usize,
281    nz: usize,
282    min: [f64; 3],
283    max: [f64; 3],
284    options: MeshGenerationOptions,
285) -> Result<GeneratedMesh, MeshSieveError> {
286    if nx == 0 || ny == 0 || nz == 0 {
287        return Err(invalid_geometry("nx, ny, and nz must be positive"));
288    }
289
290    let dx = (max[0] - min[0]) / nx as f64;
291    let dy = (max[1] - min[1]) / ny as f64;
292    let dz = (max[2] - min[2]) / nz as f64;
293    let mut vertices = Vec::with_capacity((nx + 1) * (ny + 1) * (nz + 1));
294    for k in 0..=nz {
295        let z = min[2] + dz * k as f64;
296        for j in 0..=ny {
297            let y = min[1] + dy * j as f64;
298            for i in 0..=nx {
299                let x = min[0] + dx * i as f64;
300                vertices.push(vec![x, y, z]);
301            }
302        }
303    }
304
305    let mut cells = Vec::with_capacity(nx * ny * nz);
306    let row_stride = nx + 1;
307    let slab_stride = row_stride * (ny + 1);
308    for k in 0..nz {
309        for j in 0..ny {
310            for i in 0..nx {
311                let base = k * slab_stride + j * row_stride + i;
312                let v0 = base;
313                let v1 = base + 1;
314                let v3 = base + row_stride;
315                let v2 = v3 + 1;
316                let v4 = base + slab_stride;
317                let v5 = v4 + 1;
318                let v7 = v4 + row_stride;
319                let v6 = v7 + 1;
320                cells.push(vec![v0, v1, v2, v3, v4, v5, v6, v7]);
321            }
322        }
323    }
324
325    let (mut mesh, vertex_points) = build_mesh(3, &vertices, &cells, CellType::Hexahedron)?;
326
327    let mut labels = LabelSet::new();
328    for k in 0..=nz {
329        for j in 0..=ny {
330            for i in 0..=nx {
331                let idx = k * (nx + 1) * (ny + 1) + j * (nx + 1) + i;
332                let point = vertex_points[idx];
333                if i == 0 {
334                    labels.set_label(point, BOUNDARY_X_MIN, 1);
335                }
336                if i == nx {
337                    labels.set_label(point, BOUNDARY_X_MAX, 1);
338                }
339                if j == 0 {
340                    labels.set_label(point, BOUNDARY_Y_MIN, 1);
341                }
342                if j == ny {
343                    labels.set_label(point, BOUNDARY_Y_MAX, 1);
344                }
345                if k == 0 {
346                    labels.set_label(point, BOUNDARY_Z_MIN, 1);
347                }
348                if k == nz {
349                    labels.set_label(point, BOUNDARY_Z_MAX, 1);
350                }
351            }
352        }
353    }
354    mesh.labels = Some(labels);
355
356    let periodic = if options.periodic.x || options.periodic.y || options.periodic.z {
357        let mut eq = PointEquivalence::new();
358        if options.periodic.x {
359            for k in 0..=nz {
360                for j in 0..=ny {
361                    let left_idx = k * (nx + 1) * (ny + 1) + j * (nx + 1);
362                    let right_idx = left_idx + nx;
363                    eq.add_equivalence(vertex_points[left_idx], vertex_points[right_idx]);
364                }
365            }
366        }
367        if options.periodic.y {
368            for k in 0..=nz {
369                for i in 0..=nx {
370                    let bottom_idx = k * (nx + 1) * (ny + 1) + i;
371                    let top_idx = k * (nx + 1) * (ny + 1) + ny * (nx + 1) + i;
372                    eq.add_equivalence(vertex_points[bottom_idx], vertex_points[top_idx]);
373                }
374            }
375        }
376        if options.periodic.z {
377            let slab = (nx + 1) * (ny + 1);
378            for j in 0..=ny {
379                for i in 0..=nx {
380                    let bottom_idx = j * (nx + 1) + i;
381                    let top_idx = nz * slab + j * (nx + 1) + i;
382                    eq.add_equivalence(vertex_points[bottom_idx], vertex_points[top_idx]);
383                }
384            }
385        }
386        Some(eq)
387    } else {
388        None
389    };
390
391    Ok(GeneratedMesh { mesh, periodic })
392}