use crate::block::Block;
use crate::block_face_functions::{reduce_blocks, rotate_block};
use crate::face_record::{
FaceMatch, FaceRecord, Orientation, OrientationPlane, PERMUTATION_MATRICES,
};
use crate::rotational_periodicity::create_rotation_matrix;
use crate::utils::compute_min_gcd;
use crate::Float;
pub fn extract_canonical_grid(
block: &Block,
rec: &FaceRecord,
) -> Option<(Vec<(Float, Float, Float)>, usize, usize)> {
let (raw_lo, raw_hi) = rec.bounds();
let imax = [
block.imax.saturating_sub(1),
block.jmax.saturating_sub(1),
block.kmax.saturating_sub(1),
];
let lo = [
raw_lo[0].min(imax[0]),
raw_lo[1].min(imax[1]),
raw_lo[2].min(imax[2]),
];
let hi = [
raw_hi[0].min(imax[0]),
raw_hi[1].min(imax[1]),
raw_hi[2].min(imax[2]),
];
let const_dim = rec.constant_axis()?;
let varying: Vec<usize> = (0..3).filter(|&d| d != const_dim).collect();
let d0 = varying[0]; let d1 = varying[1]; let nu = hi[d0] - lo[d0] + 1;
let nv = hi[d1] - lo[d1] + 1;
let mut grid = Vec::with_capacity(nu * nv);
for u in 0..nu {
for v in 0..nv {
let mut idx = [0usize; 3];
idx[const_dim] = lo[const_dim];
idx[d0] = lo[d0] + u;
idx[d1] = lo[d1] + v;
grid.push(block.xyz(idx[0], idx[1], idx[2]));
}
}
Some((grid, nu, nv))
}
pub fn apply_permutation(
grid: &[(Float, Float, Float)],
nu: usize,
nv: usize,
perm_idx: u8,
) -> (Vec<(Float, Float, Float)>, usize, usize) {
let _mat = PERMUTATION_MATRICES[perm_idx as usize];
let u_rev = perm_idx & 1 != 0;
let v_rev = perm_idx & 2 != 0;
let swap = perm_idx & 4 != 0;
let (out_nu, out_nv) = if swap { (nv, nu) } else { (nu, nv) };
let mut result = Vec::with_capacity(out_nu * out_nv);
for ou in 0..out_nu {
for ov in 0..out_nv {
let (gu, gv) = if swap { (ov, ou) } else { (ou, ov) };
let gu = if u_rev { nu - 1 - gu } else { gu };
let gv = if v_rev { nv - 1 - gv } else { gv };
result.push(grid[gu * nv + gv]);
}
}
(result, out_nu, out_nv)
}
pub fn verify_match(
pts_a: &[(Float, Float, Float)],
pts_b: &[(Float, Float, Float)],
tol: Float,
) -> bool {
if pts_a.len() != pts_b.len() {
return false;
}
let tol2 = tol * tol;
for (a, b) in pts_a.iter().zip(pts_b.iter()) {
let d2 = (a.0 - b.0).powi(2) + (a.1 - b.1).powi(2) + (a.2 - b.2).powi(2);
if d2 > tol2 {
return false;
}
}
true
}
fn max_point_distance(
pts_a: &[(Float, Float, Float)],
pts_b: &[(Float, Float, Float)],
) -> Float {
if pts_a.len() != pts_b.len() {
return Float::MAX;
}
let mut max_d2: Float = 0.0;
for (a, b) in pts_a.iter().zip(pts_b.iter()) {
let d2 = (a.0 - b.0).powi(2) + (a.1 - b.1).powi(2) + (a.2 - b.2).powi(2);
if d2 > max_d2 {
max_d2 = d2;
}
}
max_d2.sqrt()
}
pub fn verify_partial_match(
grid_a: &[(Float, Float, Float)],
grid_b_permuted: &[(Float, Float, Float)],
tol: Float,
) -> (usize, usize) {
let tol2 = tol * tol;
let mut count = 0;
for b in grid_b_permuted {
for a in grid_a {
let d2 = (a.0 - b.0).powi(2) + (a.1 - b.1).powi(2) + (a.2 - b.2).powi(2);
if d2 <= tol2 {
count += 1;
break;
}
}
}
(count, grid_b_permuted.len())
}
pub fn determine_plane(rec_a: &FaceRecord, rec_b: &FaceRecord) -> OrientationPlane {
if rec_a.constant_axis() == rec_b.constant_axis() {
OrientationPlane::InPlane
} else {
OrientationPlane::CrossPlane
}
}
pub fn try_all_permutations(
grid_a: &[(Float, Float, Float)],
nu_a: usize,
nv_a: usize,
grid_b: &[(Float, Float, Float)],
nu_b: usize,
nv_b: usize,
tol: Float,
) -> Option<u8> {
for perm_idx in 0u8..8 {
let (permuted, out_nu, out_nv) = apply_permutation(grid_b, nu_b, nv_b, perm_idx);
if out_nu != nu_a || out_nv != nv_a {
continue;
}
if verify_match(grid_a, &permuted, tol) {
return Some(perm_idx);
}
}
None
}
fn prepare_reduced(blocks: &[Block], face_matches: &[FaceMatch]) -> (Vec<Block>, Vec<FaceMatch>) {
let gcd_to_use = compute_min_gcd(blocks);
let reduced_blocks = reduce_blocks(blocks, gcd_to_use);
let scaled_matches: Vec<FaceMatch> = face_matches
.iter()
.map(|fm| {
let mut sfm = fm.clone();
sfm.divide_indices(gcd_to_use);
sfm
})
.collect();
(reduced_blocks, scaled_matches)
}
pub fn verify_connectivity(
blocks: &[Block],
face_matches: &[FaceMatch],
tol: Float,
) -> (Vec<FaceMatch>, Vec<FaceMatch>) {
let (reduced_blocks, scaled_matches) = prepare_reduced(blocks, face_matches);
let mut verified = Vec::new();
let mut mismatched = Vec::new();
for (idx, sfm) in scaled_matches.iter().enumerate() {
let b1 = &sfm.block1;
let b2 = &sfm.block2;
let b1_idx = b1.block_index;
let b2_idx = b2.block_index;
if b1_idx >= reduced_blocks.len() || b2_idx >= reduced_blocks.len() {
mismatched.push(face_matches[idx].clone());
continue;
}
let block1 = &reduced_blocks[b1_idx];
let block2 = &reduced_blocks[b2_idx];
let grid_a = match extract_canonical_grid(block1, b1) {
Some(g) => g,
None => {
mismatched.push(face_matches[idx].clone());
continue;
}
};
let grid_b = match extract_canonical_grid(block2, b2) {
Some(g) => g,
None => {
mismatched.push(face_matches[idx].clone());
continue;
}
};
let (pts_a, nu_a, nv_a) = grid_a;
let (pts_b, nu_b, nv_b) = grid_b;
let stored_perm = sfm.orientation.as_ref().map(|o| o.permutation_index);
if let Some(perm_idx) = stored_perm {
let (permuted, out_nu, out_nv) = apply_permutation(&pts_b, nu_b, nv_b, perm_idx);
if out_nu == nu_a && out_nv == nv_a && verify_match(&pts_a, &permuted, tol) {
verified.push(face_matches[idx].clone());
continue;
}
}
if let Some(perm_idx) = try_all_permutations(&pts_a, nu_a, nv_a, &pts_b, nu_b, nv_b, tol) {
let mut corrected = face_matches[idx].clone();
let plane = determine_plane(b1, b2);
corrected.orientation = Some(Orientation {
permutation_index: perm_idx,
plane,
});
verified.push(corrected);
} else {
if std::env::var("PLOT3D_RS_VERIFY_CONNECTIVITY_VERBOSE").as_deref() == Ok("1") {
let orig = &face_matches[idx];
let ca1 = b1.constant_axis();
let ca2 = b2.constant_axis();
let axis_label = |a: Option<usize>| match a {
Some(0) => "I", Some(1) => "J", Some(2) => "K", _ => "?"
};
let cross_tag = if ca1 != ca2 { "CROSS-AXIS" } else { "SAME-AXIS" };
let mut best_dist: Float = Float::MAX;
for p in 0u8..8 {
let (permuted, out_nu, out_nv) = apply_permutation(&pts_b, nu_b, nv_b, p);
if out_nu != nu_a || out_nv != nv_a { continue; }
let d = max_point_distance(&pts_a, &permuted);
if d < best_dist { best_dist = d; }
}
eprintln!("verify_connectivity: MISMATCH at index {} [{}]", idx, cross_tag);
eprintln!(
" block {}: lo=({},{},{}) hi=({},{},{}) const={}",
orig.block1.block_index,
orig.block1.i_lo(), orig.block1.j_lo(), orig.block1.k_lo(),
orig.block1.i_hi(), orig.block1.j_hi(), orig.block1.k_hi(),
axis_label(ca1)
);
eprintln!(
" block {}: lo=({},{},{}) hi=({},{},{}) const={}",
orig.block2.block_index,
orig.block2.i_lo(), orig.block2.j_lo(), orig.block2.k_lo(),
orig.block2.i_hi(), orig.block2.j_hi(), orig.block2.k_hi(),
axis_label(ca2)
);
eprintln!(" grid_a: {}x{}, grid_b: {}x{}, best_dist: {:.6e}", nu_a, nv_a, nu_b, nv_b, best_dist);
}
mismatched.push(face_matches[idx].clone());
}
}
(verified, mismatched)
}
pub fn verify_periodicity(
blocks: &[Block],
face_matches: &[FaceMatch],
theta: Float,
rotation_axis: char,
tol: Float,
) -> (Vec<FaceMatch>, Vec<FaceMatch>) {
let (reduced_blocks, scaled_matches) = prepare_reduced(blocks, face_matches);
let rotation_matrix_pos = create_rotation_matrix(theta, rotation_axis);
let rotation_matrix_neg = create_rotation_matrix(-theta, rotation_axis);
let rotated_blocks_pos: Vec<Block> = reduced_blocks
.iter()
.map(|b| rotate_block(b, rotation_matrix_pos))
.collect();
let rotated_blocks_neg: Vec<Block> = reduced_blocks
.iter()
.map(|b| rotate_block(b, rotation_matrix_neg))
.collect();
let mut verified = Vec::new();
let mut mismatched = Vec::new();
for (idx, sfm) in scaled_matches.iter().enumerate() {
let b1 = &sfm.block1;
let b2 = &sfm.block2;
let b1_idx = b1.block_index;
let b2_idx = b2.block_index;
if b1_idx >= reduced_blocks.len() || b2_idx >= reduced_blocks.len() {
mismatched.push(face_matches[idx].clone());
continue;
}
let block2 = &reduced_blocks[b2_idx];
let grid_b = match extract_canonical_grid(block2, b2) {
Some(g) => g,
None => {
mismatched.push(face_matches[idx].clone());
continue;
}
};
let (pts_b, nu_b, nv_b) = grid_b;
let mut found = false;
let mut best_dist: Float = Float::MAX;
let mut best_dims: Option<(usize, usize, usize, usize)> = None;
for rotated_blocks in [&rotated_blocks_pos, &rotated_blocks_neg] {
if found {
break;
}
let block1_rotated = &rotated_blocks[b1_idx];
let grid_a = match extract_canonical_grid(block1_rotated, b1) {
Some(g) => g,
None => continue,
};
let (pts_a, nu_a, nv_a) = grid_a;
if best_dims.is_none() {
best_dims = Some((nu_a, nv_a, nu_b, nv_b));
}
let stored_perm = sfm.orientation.as_ref().map(|o| o.permutation_index);
if let Some(perm_idx) = stored_perm {
let (permuted, out_nu, out_nv) = apply_permutation(&pts_b, nu_b, nv_b, perm_idx);
if out_nu == nu_a && out_nv == nv_a && verify_match(&pts_a, &permuted, tol) {
verified.push(face_matches[idx].clone());
found = true;
break;
}
}
if let Some(perm_idx) =
try_all_permutations(&pts_a, nu_a, nv_a, &pts_b, nu_b, nv_b, tol)
{
let mut corrected = face_matches[idx].clone();
let plane = determine_plane(b1, b2);
corrected.orientation = Some(Orientation {
permutation_index: perm_idx,
plane,
});
verified.push(corrected);
found = true;
break;
}
for p in 0u8..8 {
let (permuted, out_nu, out_nv) = apply_permutation(&pts_b, nu_b, nv_b, p);
if out_nu != nu_a || out_nv != nv_a { continue; }
let d = max_point_distance(&pts_a, &permuted);
if d < best_dist { best_dist = d; }
}
}
if !found {
if std::env::var("PLOT3D_RS_VERIFY_PERIODICITY_VERBOSE").as_deref() == Ok("1") {
let orig = &face_matches[idx];
let ca1 = b1.constant_axis();
let ca2 = b2.constant_axis();
let axis_label = |a: Option<usize>| match a {
Some(0) => "I", Some(1) => "J", Some(2) => "K", _ => "?"
};
let cross_tag = if ca1 != ca2 { "CROSS-AXIS" } else { "SAME-AXIS" };
eprintln!("verify_periodicity: MISMATCH at index {} [{}]", idx, cross_tag);
eprintln!(
" block {}: lo=({},{},{}) hi=({},{},{}) const={}",
orig.block1.block_index,
orig.block1.i_lo(), orig.block1.j_lo(), orig.block1.k_lo(),
orig.block1.i_hi(), orig.block1.j_hi(), orig.block1.k_hi(),
axis_label(ca1)
);
eprintln!(
" block {}: lo=({},{},{}) hi=({},{},{}) const={}",
orig.block2.block_index,
orig.block2.i_lo(), orig.block2.j_lo(), orig.block2.k_lo(),
orig.block2.i_hi(), orig.block2.j_hi(), orig.block2.k_hi(),
axis_label(ca2)
);
if let Some((nua, nva, nub, nvb)) = best_dims {
eprintln!(" grid_a: {}x{}, grid_b: {}x{}, best_dist: {:.6e}", nua, nva, nub, nvb, best_dist);
}
}
let _ = best_dist; let _ = best_dims;
mismatched.push(face_matches[idx].clone());
}
}
(verified, mismatched)
}
pub fn verify_translational_periodicity(
blocks: &[Block],
face_matches: &[FaceMatch],
delta: Option<Float>,
axis: char,
tol: Float,
) -> (Vec<FaceMatch>, Vec<FaceMatch>) {
let (reduced_blocks, scaled_matches) = prepare_reduced(blocks, face_matches);
let axis_idx = match axis {
'x' | 'X' => 0usize,
'y' | 'Y' => 1usize,
'z' | 'Z' => 2usize,
_ => panic!("verify_translational_periodicity: invalid axis {:?}", axis),
};
let face_axis_centroid = |block: &Block, rec: &FaceRecord| -> Float {
let (il, jh, kl) = (rec.i_lo(), rec.j_lo(), rec.k_lo());
let (ih, jl, kh) = (rec.i_hi(), rec.j_hi(), rec.k_hi());
let (i0, i1) = if il <= ih { (il, ih) } else { (ih, il) };
let (j0, j1) = if jl <= jh { (jl, jh) } else { (jh, jl) };
let (k0, k1) = if kl <= kh { (kl, kh) } else { (kh, kl) };
let mut sum: Float = 0.0;
let mut n: usize = 0;
for k in k0..=k1 {
for j in j0..=j1 {
for i in i0..=i1 {
let (x, y, z) = block.xyz(i, j, k);
let v = match axis_idx {
0 => x,
1 => y,
_ => z,
};
sum += v;
n += 1;
}
}
}
sum / (n.max(1) as Float)
};
let mut verified = Vec::new();
let mut mismatched = Vec::new();
for (idx, sfm) in scaled_matches.iter().enumerate() {
let b1 = &sfm.block1;
let b2 = &sfm.block2;
let b1_idx = b1.block_index;
let b2_idx = b2.block_index;
if b1_idx >= reduced_blocks.len() || b2_idx >= reduced_blocks.len() {
mismatched.push(face_matches[idx].clone());
continue;
}
let block1 = &reduced_blocks[b1_idx];
let block2 = &reduced_blocks[b2_idx];
let delta_axis = match delta {
Some(d) => d,
None => {
let c1 = face_axis_centroid(block1, b1);
let c2 = face_axis_centroid(block2, b2);
(c2 - c1).abs()
}
};
if delta_axis.abs() < tol {
mismatched.push(face_matches[idx].clone());
continue;
}
let block1_shifted_pos = block1.shifted(delta_axis, axis);
let block1_shifted_neg = block1.shifted(-delta_axis, axis);
let grid_b = match extract_canonical_grid(block2, b2) {
Some(g) => g,
None => {
mismatched.push(face_matches[idx].clone());
continue;
}
};
let (pts_b, nu_b, nv_b) = grid_b;
let mut found = false;
let mut best_dist: Float = Float::MAX;
let mut best_dims: Option<(usize, usize, usize, usize)> = None;
for block1_shifted in [&block1_shifted_pos, &block1_shifted_neg] {
if found {
break;
}
let grid_a = match extract_canonical_grid(block1_shifted, b1) {
Some(g) => g,
None => continue,
};
let (pts_a, nu_a, nv_a) = grid_a;
if best_dims.is_none() {
best_dims = Some((nu_a, nv_a, nu_b, nv_b));
}
let stored_perm = sfm.orientation.as_ref().map(|o| o.permutation_index);
if let Some(perm_idx) = stored_perm {
let (permuted, out_nu, out_nv) =
apply_permutation(&pts_b, nu_b, nv_b, perm_idx);
if out_nu == nu_a && out_nv == nv_a && verify_match(&pts_a, &permuted, tol) {
verified.push(face_matches[idx].clone());
found = true;
break;
}
}
if let Some(perm_idx) =
try_all_permutations(&pts_a, nu_a, nv_a, &pts_b, nu_b, nv_b, tol)
{
let mut corrected = face_matches[idx].clone();
let plane = determine_plane(b1, b2);
corrected.orientation = Some(Orientation {
permutation_index: perm_idx,
plane,
});
verified.push(corrected);
found = true;
break;
}
for p in 0u8..8 {
let (permuted, out_nu, out_nv) =
apply_permutation(&pts_b, nu_b, nv_b, p);
if out_nu != nu_a || out_nv != nv_a {
continue;
}
let d = max_point_distance(&pts_a, &permuted);
if d < best_dist {
best_dist = d;
}
}
}
if !found {
if std::env::var("PLOT3D_RS_VERIFY_TRANSLATIONAL_VERBOSE").as_deref() == Ok("1") {
let orig = &face_matches[idx];
let ca1 = b1.constant_axis();
let ca2 = b2.constant_axis();
let axis_label = |a: Option<usize>| match a {
Some(0) => "I",
Some(1) => "J",
Some(2) => "K",
_ => "?",
};
let cross_tag = if ca1 != ca2 { "CROSS-AXIS" } else { "SAME-AXIS" };
eprintln!(
"verify_translational_periodicity[{}, Δ_per_match={:+.3e}]: \
MISMATCH at index {} [{}]",
axis, delta_axis, idx, cross_tag,
);
eprintln!(
" block {}: lo=({},{},{}) hi=({},{},{}) const={}",
orig.block1.block_index,
orig.block1.i_lo(), orig.block1.j_lo(), orig.block1.k_lo(),
orig.block1.i_hi(), orig.block1.j_hi(), orig.block1.k_hi(),
axis_label(ca1),
);
eprintln!(
" block {}: lo=({},{},{}) hi=({},{},{}) const={}",
orig.block2.block_index,
orig.block2.i_lo(), orig.block2.j_lo(), orig.block2.k_lo(),
orig.block2.i_hi(), orig.block2.j_hi(), orig.block2.k_hi(),
axis_label(ca2),
);
if let Some((nua, nva, nub, nvb)) = best_dims {
eprintln!(
" grid_a: {}x{}, grid_b: {}x{}, best_dist: {:.6e}",
nua, nva, nub, nvb, best_dist,
);
}
}
let _ = best_dims;
mismatched.push(face_matches[idx].clone());
}
}
(verified, mismatched)
}