use crate::block::Block;
use crate::dual_graph::{build_cell_graph, cell_index, CellGraph};
use crate::face_record::{FaceMatch, FaceRecord};
use crate::metrics::{compute_cell_centers, compute_cell_volumes, compute_face_metrics};
use crate::Float;
#[derive(Clone, Debug)]
pub struct FlatMesh {
pub n_cells: usize,
pub cell_volume: Vec<Float>,
pub cell_center_x: Vec<Float>,
pub cell_center_y: Vec<Float>,
pub cell_center_z: Vec<Float>,
pub n_faces: usize,
pub face_owner: Vec<u32>,
pub face_neighbor: Vec<i32>,
pub face_area_x: Vec<Float>,
pub face_area_y: Vec<Float>,
pub face_area_z: Vec<Float>,
pub face_centroid_x: Vec<Float>,
pub face_centroid_y: Vec<Float>,
pub face_centroid_z: Vec<Float>,
pub face_surface_id: Vec<i32>,
pub cell_block_id: Vec<u32>,
pub cell_local_id: Vec<u32>,
}
impl FlatMesh {
pub fn stats(&self) -> String {
let n_boundary = self.face_neighbor.iter().filter(|&&n| n < 0).count();
let n_interior = self.n_faces - n_boundary;
let (min_vol, max_vol) = if self.cell_volume.is_empty() {
(0.0 as Float, 0.0 as Float)
} else {
let min_v = self.cell_volume.iter().cloned().fold(Float::INFINITY, Float::min);
let max_v = self.cell_volume.iter().cloned().fold(Float::NEG_INFINITY, Float::max);
(min_v, max_v)
};
let total_vol: Float = self.cell_volume.iter().sum();
format!(
"FlatMesh statistics:\n\
\x20 Cells: {}\n\
\x20 Faces (total): {}\n\
\x20 Interior: {}\n\
\x20 Boundary: {}\n\
\x20 Volume (total): {:.6e}\n\
\x20 Volume (min): {:.6e}\n\
\x20 Volume (max): {:.6e}",
self.n_cells, self.n_faces, n_interior, n_boundary,
total_vol, min_vol, max_vol,
)
}
}
pub fn build_flat_mesh(
blocks: &[Block],
face_matches: &[FaceMatch],
outer_faces: &[FaceRecord],
) -> FlatMesh {
let graph = build_cell_graph(blocks, face_matches);
let n_cells = graph.n_cells;
let mut cell_volume = vec![0.0 as Float; n_cells];
let mut cell_center_x = vec![0.0 as Float; n_cells];
let mut cell_center_y = vec![0.0 as Float; n_cells];
let mut cell_center_z = vec![0.0 as Float; n_cells];
let mut cell_block_id = vec![0u32; n_cells];
let mut cell_local_id = vec![0u32; n_cells];
for (b, blk) in blocks.iter().enumerate() {
let vols = compute_cell_volumes(blk);
let (xc, yc, zc) = compute_cell_centers(blk);
let offset = graph.block_offset[b];
let n_local = vols.len();
for local_id in 0..n_local {
let gid = offset + local_id;
cell_volume[gid] = vols[local_id];
cell_center_x[gid] = xc[local_id];
cell_center_y[gid] = yc[local_id];
cell_center_z[gid] = zc[local_id];
cell_block_id[gid] = b as u32;
cell_local_id[gid] = local_id as u32;
}
}
let all_face_metrics: Vec<_> = blocks.iter().map(|blk| compute_face_metrics(blk)).collect();
let mut face_owner: Vec<u32> = Vec::new();
let mut face_neighbor: Vec<i32> = Vec::new();
let mut face_area_x: Vec<Float> = Vec::new();
let mut face_area_y: Vec<Float> = Vec::new();
let mut face_area_z: Vec<Float> = Vec::new();
let mut face_centroid_x: Vec<Float> = Vec::new();
let mut face_centroid_y: Vec<Float> = Vec::new();
let mut face_centroid_z: Vec<Float> = Vec::new();
let mut face_surface_id: Vec<i32> = Vec::new();
for (b, blk) in blocks.iter().enumerate() {
let ni = blk.imax;
let nj = blk.jmax;
let nk = blk.kmax;
let nci = ni - 1;
let ncj = nj - 1;
let nck = nk - 1;
let offset = graph.block_offset[b];
let fm = &all_face_metrics[b];
for k in 0..nck {
for j in 0..ncj {
for i in 1..nci {
let owner_local = cell_index(i - 1, j, k, nci, ncj);
let neighbor_local = cell_index(i, j, k, nci, ncj);
let fid = i + ni * j + ni * (nj - 1) * k;
face_owner.push((offset + owner_local) as u32);
face_neighbor.push((offset + neighbor_local) as i32);
face_area_x.push(fm.si_x[fid]);
face_area_y.push(fm.si_y[fid]);
face_area_z.push(fm.si_z[fid]);
face_centroid_x.push(fm.ci_x[fid]);
face_centroid_y.push(fm.ci_y[fid]);
face_centroid_z.push(fm.ci_z[fid]);
face_surface_id.push(-1);
}
}
}
for k in 0..nck {
for j in 1..ncj {
for i in 0..nci {
let owner_local = cell_index(i, j - 1, k, nci, ncj);
let neighbor_local = cell_index(i, j, k, nci, ncj);
let fid = i + (ni - 1) * j + (ni - 1) * nj * k;
face_owner.push((offset + owner_local) as u32);
face_neighbor.push((offset + neighbor_local) as i32);
face_area_x.push(fm.sj_x[fid]);
face_area_y.push(fm.sj_y[fid]);
face_area_z.push(fm.sj_z[fid]);
face_centroid_x.push(fm.cj_x[fid]);
face_centroid_y.push(fm.cj_y[fid]);
face_centroid_z.push(fm.cj_z[fid]);
face_surface_id.push(-1);
}
}
}
for k in 1..nck {
for j in 0..ncj {
for i in 0..nci {
let owner_local = cell_index(i, j, k - 1, nci, ncj);
let neighbor_local = cell_index(i, j, k, nci, ncj);
let fid = i + (ni - 1) * j + (ni - 1) * (nj - 1) * k;
face_owner.push((offset + owner_local) as u32);
face_neighbor.push((offset + neighbor_local) as i32);
face_area_x.push(fm.sk_x[fid]);
face_area_y.push(fm.sk_y[fid]);
face_area_z.push(fm.sk_z[fid]);
face_centroid_x.push(fm.ck_x[fid]);
face_centroid_y.push(fm.ck_y[fid]);
face_centroid_z.push(fm.ck_z[fid]);
face_surface_id.push(-1);
}
}
}
}
for fm_match in face_matches {
let b1 = fm_match.block1.block_index;
let b2 = fm_match.block2.block_index;
let edges = cross_block_face_data(
b1,
&fm_match.block1,
&blocks[b1],
b2,
&fm_match.block2,
&blocks[b2],
&graph,
&all_face_metrics[b1],
);
for (owner, neighbor, ax, ay, az, cx, cy, cz) in edges {
face_owner.push(owner);
face_neighbor.push(neighbor as i32);
face_area_x.push(ax);
face_area_y.push(ay);
face_area_z.push(az);
face_centroid_x.push(cx);
face_centroid_y.push(cy);
face_centroid_z.push(cz);
face_surface_id.push(-1);
}
}
for oface in outer_faces {
let b = oface.block_index;
let blk = &blocks[b];
let ni = blk.imax;
let nj = blk.jmax;
let nk = blk.kmax;
let nci = ni - 1;
let ncj = nj - 1;
let _nck = nk - 1;
let offset = graph.block_offset[b];
let fm = &all_face_metrics[b];
let surface_id = oface.id.map(|id| id as i32).unwrap_or(0);
let const_axis = oface.constant_axis();
if const_axis.is_none() {
continue; }
let axis = const_axis.unwrap();
let const_vals = [oface.i_lo(), oface.j_lo(), oface.k_lo()];
let const_v = const_vals[axis];
let n_nodes = [ni, nj, nk];
let is_high = const_v == n_nodes[axis] - 1;
let var_axes: Vec<usize> = (0..3).filter(|&a| a != axis).collect();
let lo = [oface.i_lo(), oface.j_lo(), oface.k_lo()];
let hi = [oface.i_hi(), oface.j_hi(), oface.k_hi()];
let n_u = hi[var_axes[0]] - lo[var_axes[0]];
let n_v = hi[var_axes[1]] - lo[var_axes[1]];
if n_u == 0 || n_v == 0 {
continue; }
let cell_const = if is_high {
n_nodes[axis] - 2
} else {
0
};
for v in 0..n_v {
for u in 0..n_u {
let mut ijk = [0usize; 3];
ijk[axis] = cell_const;
ijk[var_axes[0]] = lo[var_axes[0]] + u;
ijk[var_axes[1]] = lo[var_axes[1]] + v;
let gid = offset + cell_index(ijk[0], ijk[1], ijk[2], nci, ncj);
let (ax, ay, az) = boundary_face_area(
axis, const_v, ijk, blk, fm,
);
let (cx, cy, cz) = boundary_face_centroid(
axis, const_v, ijk, blk, fm,
);
face_owner.push(gid as u32);
face_neighbor.push(-1);
if !is_high {
face_area_x.push(-ax);
face_area_y.push(-ay);
face_area_z.push(-az);
} else {
face_area_x.push(ax);
face_area_y.push(ay);
face_area_z.push(az);
}
face_centroid_x.push(cx);
face_centroid_y.push(cy);
face_centroid_z.push(cz);
face_surface_id.push(surface_id);
}
}
}
let n_faces = face_owner.len();
FlatMesh {
n_cells,
cell_volume,
cell_center_x,
cell_center_y,
cell_center_z,
n_faces,
face_owner,
face_neighbor,
face_area_x,
face_area_y,
face_area_z,
face_centroid_x,
face_centroid_y,
face_centroid_z,
face_surface_id,
cell_block_id,
cell_local_id,
}
}
fn boundary_face_area(
axis: usize,
const_v: usize,
ijk: [usize; 3],
blk: &Block,
fm: &crate::metrics::FaceMetrics,
) -> (Float, Float, Float) {
let ni = blk.imax;
let nj = blk.jmax;
match axis {
0 => {
let fid = const_v + ni * ijk[1] + ni * (nj - 1) * ijk[2];
(fm.si_x[fid], fm.si_y[fid], fm.si_z[fid])
}
1 => {
let fid = ijk[0] + (ni - 1) * const_v + (ni - 1) * nj * ijk[2];
(fm.sj_x[fid], fm.sj_y[fid], fm.sj_z[fid])
}
2 => {
let fid = ijk[0] + (ni - 1) * ijk[1] + (ni - 1) * (nj - 1) * const_v;
(fm.sk_x[fid], fm.sk_y[fid], fm.sk_z[fid])
}
_ => unreachable!("axis must be 0, 1, or 2"),
}
}
fn boundary_face_centroid(
axis: usize,
const_v: usize,
ijk: [usize; 3],
blk: &Block,
fm: &crate::metrics::FaceMetrics,
) -> (Float, Float, Float) {
let ni = blk.imax;
let nj = blk.jmax;
match axis {
0 => {
let fid = const_v + ni * ijk[1] + ni * (nj - 1) * ijk[2];
(fm.ci_x[fid], fm.ci_y[fid], fm.ci_z[fid])
}
1 => {
let fid = ijk[0] + (ni - 1) * const_v + (ni - 1) * nj * ijk[2];
(fm.cj_x[fid], fm.cj_y[fid], fm.cj_z[fid])
}
2 => {
let fid = ijk[0] + (ni - 1) * ijk[1] + (ni - 1) * (nj - 1) * const_v;
(fm.ck_x[fid], fm.ck_y[fid], fm.ck_z[fid])
}
_ => unreachable!("axis must be 0, 1, or 2"),
}
}
fn cross_block_face_data(
b1: usize,
face1: &FaceRecord,
blk1: &Block,
b2: usize,
face2: &FaceRecord,
blk2: &Block,
graph: &CellGraph,
fm1: &crate::metrics::FaceMetrics,
) -> Vec<(u32, u32, Float, Float, Float, Float, Float, Float)> {
let mut result = Vec::new();
let axis1 = match face1.constant_axis() {
Some(a) => a,
None => return result,
};
let axis2 = match face2.constant_axis() {
Some(a) => a,
None => return result,
};
let f1_bounds = face1.bounds();
let f2_bounds = face2.bounds();
let f1_const_val = f1_bounds.0[axis1];
let f2_const_val = f2_bounds.0[axis2];
let n_nodes1 = [blk1.imax, blk1.jmax, blk1.kmax];
let n_nodes2 = [blk2.imax, blk2.jmax, blk2.kmax];
let cell1_const = if f1_const_val == 0 {
0
} else if f1_const_val == n_nodes1[axis1] - 1 {
n_nodes1[axis1] - 2
} else {
return result;
};
let cell2_const = if f2_const_val == 0 {
0
} else if f2_const_val == n_nodes2[axis2] - 1 {
n_nodes2[axis2] - 2
} else {
return result;
};
let is_high1 = f1_const_val == n_nodes1[axis1] - 1;
let var_axes1: Vec<usize> = (0..3).filter(|&a| a != axis1).collect();
let var_axes2: Vec<usize> = (0..3).filter(|&a| a != axis2).collect();
let f1_lo = [face1.i_lo(), face1.j_lo(), face1.k_lo()];
let f1_hi = [face1.i_hi(), face1.j_hi(), face1.k_hi()];
let f2_lo = [face2.i_lo(), face2.j_lo(), face2.k_lo()];
let f2_hi = [face2.i_hi(), face2.j_hi(), face2.k_hi()];
let n_u1 = f1_hi[var_axes1[0]] - f1_lo[var_axes1[0]];
let n_v1 = f1_hi[var_axes1[1]] - f1_lo[var_axes1[1]];
let n_u2 = f2_hi[var_axes2[0]] - f2_lo[var_axes2[0]];
let n_v2 = f2_hi[var_axes2[1]] - f2_lo[var_axes2[1]];
if n_u1 == 0 || n_v1 == 0 {
return result;
}
let swapped = (n_u1 == n_v2) && (n_v1 == n_u2) && !((n_u1 == n_u2) && (n_v1 == n_v2));
let f2_raw = [
[face2.il, face2.ih],
[face2.jl, face2.jh],
[face2.kl, face2.kh],
];
let f2_u_reversed = f2_raw[var_axes2[0]][0] > f2_raw[var_axes2[0]][1];
let f2_v_reversed = f2_raw[var_axes2[1]][0] > f2_raw[var_axes2[1]][1];
let (nci1, ncj1, _) = graph.block_cell_dims[b1];
let (nci2, ncj2, _) = graph.block_cell_dims[b2];
for v in 0..n_v1 {
for u in 0..n_u1 {
let mut ijk1 = [0usize; 3];
ijk1[axis1] = cell1_const;
ijk1[var_axes1[0]] = f1_lo[var_axes1[0]] + u;
ijk1[var_axes1[1]] = f1_lo[var_axes1[1]] + v;
let (u2, v2) = if swapped { (v, u) } else { (u, v) };
let u2_mapped = if f2_u_reversed { n_u2 - 1 - u2 } else { u2 };
let v2_mapped = if f2_v_reversed { n_v2 - 1 - v2 } else { v2 };
let mut ijk2 = [0usize; 3];
ijk2[axis2] = cell2_const;
ijk2[var_axes2[0]] = f2_lo[var_axes2[0]] + u2_mapped;
ijk2[var_axes2[1]] = f2_lo[var_axes2[1]] + v2_mapped;
let gid1 = graph.block_offset[b1]
+ cell_index(ijk1[0], ijk1[1], ijk1[2], nci1, ncj1);
let gid2 = graph.block_offset[b2]
+ cell_index(ijk2[0], ijk2[1], ijk2[2], nci2, ncj2);
let (mut ax, mut ay, mut az) = boundary_face_area(
axis1, f1_const_val, ijk1, blk1, fm1,
);
if !is_high1 {
ax = -ax;
ay = -ay;
az = -az;
}
let (cx, cy, cz) = boundary_face_centroid(
axis1, f1_const_val, ijk1, blk1, fm1,
);
result.push((gid1 as u32, gid2 as u32, ax, ay, az, cx, cy, cz));
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::Block;
use crate::face_record::{FaceMatch, FaceRecord};
fn uniform_block(
ni: usize, nj: usize, nk: usize,
x0: f64, x1: f64, y0: f64, y1: f64, z0: f64, z1: f64,
) -> Block {
let n = ni * nj * nk;
let mut x = Vec::with_capacity(n);
let mut y = Vec::with_capacity(n);
let mut z = Vec::with_capacity(n);
let dx = if ni > 1 { (x1 - x0) / (ni as f64 - 1.0) } else { 0.0 };
let dy = if nj > 1 { (y1 - y0) / (nj as f64 - 1.0) } else { 0.0 };
let dz = if nk > 1 { (z1 - z0) / (nk as f64 - 1.0) } else { 0.0 };
for k in 0..nk {
for j in 0..nj {
for i in 0..ni {
x.push(x0 + i as f64 * dx);
y.push(y0 + j as f64 * dy);
z.push(z0 + k as f64 * dz);
}
}
}
Block::new(ni, nj, nk, x, y, z)
}
#[test]
fn test_single_block_flat_mesh() {
let blk = uniform_block(3, 3, 3, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0);
let outer_faces = vec![
FaceRecord { block_index: 0, il: 0, jl: 0, kl: 0, ih: 0, jh: 2, kh: 2, id: Some(1), u_physical: None, v_physical: None },
FaceRecord { block_index: 0, il: 2, jl: 0, kl: 0, ih: 2, jh: 2, kh: 2, id: Some(2), u_physical: None, v_physical: None },
FaceRecord { block_index: 0, il: 0, jl: 0, kl: 0, ih: 2, jh: 0, kh: 2, id: Some(3), u_physical: None, v_physical: None },
FaceRecord { block_index: 0, il: 0, jl: 2, kl: 0, ih: 2, jh: 2, kh: 2, id: Some(4), u_physical: None, v_physical: None },
FaceRecord { block_index: 0, il: 0, jl: 0, kl: 0, ih: 2, jh: 2, kh: 0, id: Some(5), u_physical: None, v_physical: None },
FaceRecord { block_index: 0, il: 0, jl: 0, kl: 2, ih: 2, jh: 2, kh: 2, id: Some(6), u_physical: None, v_physical: None },
];
let mesh = build_flat_mesh(&[blk], &[], &outer_faces);
assert_eq!(mesh.n_cells, 8);
assert_eq!(mesh.cell_volume.len(), 8);
for v in &mesh.cell_volume {
assert!((v - 0.125).abs() < 1e-10, "Expected 0.125, got {}", v);
}
let total_vol: f64 = mesh.cell_volume.iter().sum();
assert!((total_vol - 1.0).abs() < 1e-10);
let n_boundary = mesh.face_neighbor.iter().filter(|&&n| n < 0).count();
assert_eq!(n_boundary, 24, "Expected 24 boundary faces, got {}", n_boundary);
let n_interior = mesh.n_faces - n_boundary;
assert_eq!(n_interior, 12, "Expected 12 interior faces, got {}", n_interior);
let stats = mesh.stats();
assert!(stats.contains("Cells:"));
}
#[test]
fn test_two_block_flat_mesh() {
let blk0 = uniform_block(3, 3, 3, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0);
let blk1 = uniform_block(3, 3, 3, 1.0, 2.0, 0.0, 1.0, 0.0, 1.0);
let fm = FaceMatch {
block1: FaceRecord {
block_index: 0, il: 2, jl: 0, kl: 0, ih: 2, jh: 2, kh: 2,
id: None, u_physical: None, v_physical: None,
},
block2: FaceRecord {
block_index: 1, il: 0, jl: 0, kl: 0, ih: 0, jh: 2, kh: 2,
id: None, u_physical: None, v_physical: None,
},
points: vec![],
orientation: None,
};
let mesh = build_flat_mesh(&[blk0, blk1], &[fm], &[]);
assert_eq!(mesh.n_cells, 16);
let cross_faces: Vec<_> = (0..mesh.n_faces)
.filter(|&f| {
let o = mesh.face_owner[f] as usize;
let n = mesh.face_neighbor[f];
if n < 0 { return false; }
let n = n as usize;
mesh.cell_block_id[o] != mesh.cell_block_id[n]
})
.collect();
assert_eq!(cross_faces.len(), 4, "Expected 4 cross-block faces");
}
}