use crate::block::Block;
use crate::face_record::FaceMatch;
#[derive(Clone, Debug)]
pub struct CellGraph {
pub n_cells: usize,
pub block_offset: Vec<usize>,
pub block_cell_dims: Vec<(usize, usize, usize)>,
pub cross_block_edges: Vec<(usize, usize)>,
}
#[inline]
pub fn cell_index(i: usize, j: usize, k: usize, nci: usize, ncj: usize) -> usize {
debug_assert!(i < nci, "cell i={} out of range [0, {})", i, nci);
debug_assert!(j < ncj, "cell j={} out of range [0, {})", j, ncj);
(k * ncj + j) * nci + i
}
#[inline]
pub fn global_cell_id(block: usize, i: usize, j: usize, k: usize, graph: &CellGraph) -> usize {
let (nci, ncj, _) = graph.block_cell_dims[block];
graph.block_offset[block] + cell_index(i, j, k, nci, ncj)
}
pub fn build_cell_graph(blocks: &[Block], face_matches: &[FaceMatch]) -> CellGraph {
let n_blocks = blocks.len();
let mut block_cell_dims = Vec::with_capacity(n_blocks);
let mut block_offset = Vec::with_capacity(n_blocks);
let mut cumulative = 0usize;
for blk in blocks {
let nci = blk.imax - 1;
let ncj = blk.jmax - 1;
let nck = blk.kmax - 1;
block_cell_dims.push((nci, ncj, nck));
block_offset.push(cumulative);
cumulative += nci * ncj * nck;
}
let n_cells = cumulative;
let mut cross_block_edges = Vec::new();
for fm in face_matches {
let b1 = fm.block1.block_index;
let b2 = fm.block2.block_index;
let edges = build_cross_block_face_edges(
b1,
&fm.block1,
&blocks[b1],
b2,
&fm.block2,
&blocks[b2],
&block_cell_dims,
&block_offset,
fm.orientation.as_ref(),
);
cross_block_edges.extend(edges);
}
CellGraph {
n_cells,
block_offset,
block_cell_dims,
cross_block_edges,
}
}
fn boundary_cell_index(face_val: usize, n_nodes: usize) -> Option<usize> {
if face_val == 0 {
Some(0)
} else if face_val == n_nodes - 1 {
Some(n_nodes - 2)
} else {
None
}
}
fn build_cross_block_face_edges(
b1: usize,
face1: &crate::face_record::FaceRecord,
blk1: &Block,
b2: usize,
face2: &crate::face_record::FaceRecord,
blk2: &Block,
block_cell_dims: &[(usize, usize, usize)],
block_offset: &[usize],
orientation: Option<&crate::face_record::Orientation>,
) -> Vec<(usize, usize)> {
let mut edges = Vec::new();
let f1_const_axis = face1.constant_axis();
let f2_const_axis = face2.constant_axis();
let (axis1, axis2) = match (f1_const_axis, f2_const_axis) {
(Some(a1), Some(a2)) => (a1, a2),
_ => return edges, };
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 = match boundary_cell_index(f1_const_val, n_nodes1[axis1]) {
Some(c) => c,
None => return edges,
};
let cell2_const = match boundary_cell_index(f2_const_val, n_nodes2[axis2]) {
Some(c) => c,
None => return edges,
};
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 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 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_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 edges; }
let f2_raw = [
[face2.il, face2.ih],
[face2.jl, face2.jh],
[face2.kl, face2.kh],
];
let (swapped, f2_u_reversed, f2_v_reversed) = match orientation {
Some(o) => {
let pi = o.permutation_index;
(
(pi & 0b100) != 0,
(pi & 0b001) != 0,
(pi & 0b010) != 0,
)
}
None => {
let swap = (n_u1 == n_v2)
&& (n_v1 == n_u2)
&& !((n_u1 == n_u2) && (n_v1 == n_v2));
let u_rev = f2_raw[var_axes2[0]][0] > f2_raw[var_axes2[0]][1];
let v_rev = f2_raw[var_axes2[1]][0] > f2_raw[var_axes2[1]][1];
(swap, u_rev, v_rev)
}
};
let (nci1, ncj1, _) = block_cell_dims[b1];
let (nci2, ncj2, _) = 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 mut ijk2 = [0usize; 3];
ijk2[axis2] = cell2_const;
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
};
ijk2[var_axes2[0]] = f2_lo[var_axes2[0]] + u2_mapped;
ijk2[var_axes2[1]] = f2_lo[var_axes2[1]] + v2_mapped;
let gid1 = block_offset[b1] + cell_index(ijk1[0], ijk1[1], ijk1[2], nci1, ncj1);
let gid2 = block_offset[b2] + cell_index(ijk2[0], ijk2[1], ijk2[2], nci2, ncj2);
edges.push((gid1, gid2));
}
}
edges
}
#[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_graph() {
let block = uniform_block(3, 3, 3, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0);
let graph = build_cell_graph(&[block], &[]);
assert_eq!(graph.n_cells, 8);
assert_eq!(graph.block_offset, vec![0]);
assert_eq!(graph.block_cell_dims, vec![(2, 2, 2)]);
assert!(graph.cross_block_edges.is_empty());
}
#[test]
fn test_two_blocks_with_interface() {
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 graph = build_cell_graph(&[blk0, blk1], &[fm]);
assert_eq!(graph.n_cells, 16); assert_eq!(graph.block_offset, vec![0, 8]);
assert_eq!(graph.cross_block_edges.len(), 4);
}
#[test]
fn test_cell_index_consistency() {
assert_eq!(cell_index(0, 0, 0, 3, 4), 0);
assert_eq!(cell_index(1, 0, 0, 3, 4), 1);
assert_eq!(cell_index(0, 1, 0, 3, 4), 3);
assert_eq!(cell_index(0, 0, 1, 3, 4), 12);
}
#[test]
fn test_global_cell_id() {
let blk0 = uniform_block(3, 3, 3, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0);
let blk1 = uniform_block(4, 3, 3, 1.0, 2.0, 0.0, 1.0, 0.0, 1.0);
let graph = build_cell_graph(&[blk0, blk1], &[]);
assert_eq!(graph.block_offset[1], 8);
assert_eq!(global_cell_id(1, 0, 0, 0, &graph), 8);
assert_eq!(global_cell_id(0, 1, 1, 1, &graph), 1 + 2 + 4);
}
}