use crate::mesh::fv_mesh::{BoundaryPatch, FvMesh, FvMeshBuilder, PatchKind};
use crate::primitives::Vector3;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AmiOverlap {
pub source: usize,
pub overlap_area: f64,
pub weight: f64,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AmiWeight {
pub source_face: usize,
pub source_cell: usize,
pub weight: f64,
pub overlap_area: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct AmiCoupling {
pub target_face: usize,
pub target_cell: usize,
pub target_patch: usize,
pub source_patch: usize,
pub local: usize,
pub weights: Vec<AmiWeight>,
}
impl AmiCoupling {
pub fn weight_sum(&self) -> f64 {
self.weights.iter().map(|w| w.weight).sum()
}
}
#[inline]
fn interval_overlap(a0: f64, a1: f64, b0: f64, b1: f64) -> f64 {
(a1.min(b1) - a0.max(b0)).max(0.0)
}
pub fn overlap_weights_1d(
target_spans: &[(f64, f64)],
source_spans: &[(f64, f64)],
depth: f64,
) -> Vec<Vec<AmiOverlap>> {
assert!(depth > 0.0, "AMI depth must be positive, got {depth}");
let mut out = Vec::with_capacity(target_spans.len());
for &(t0, t1) in target_spans {
assert!(t1 > t0, "degenerate target span ({t0}, {t1})");
let t_len = t1 - t0;
let t_area = t_len * depth;
let mut row = Vec::new();
for (j, &(s0, s1)) in source_spans.iter().enumerate() {
let ov_len = interval_overlap(t0, t1, s0, s1);
if ov_len <= 0.0 {
continue;
}
let overlap_area = ov_len * depth;
row.push(AmiOverlap {
source: j,
overlap_area,
weight: overlap_area / t_area,
});
}
out.push(row);
}
out
}
impl FvMesh {
pub fn ami_ldu_start(&self) -> usize {
self.n_internal_faces + self.cyclic_couplings.len()
}
pub fn n_ami_faces(&self) -> usize {
self.ami_couplings.iter().map(|c| c.weights.len()).sum()
}
pub fn periodic_ring_ami(n_a: usize, n_b: usize, lx: f64, ly: f64, depth: f64) -> FvMesh {
assert!(n_a >= 1 && n_b >= 1, "periodic_ring_ami needs n_a,n_b ≥ 1");
assert!(
lx > 0.0 && ly > 0.0 && depth > 0.0,
"lx, ly, depth must be > 0"
);
let dy_a = ly / n_a as f64;
let dy_b = ly / n_b as f64;
let a_area = dy_a * depth; let b_area = dy_b * depth; let n_cells = n_a + n_b;
let mut cell_centres = Vec::with_capacity(n_cells);
let mut cell_volumes = Vec::with_capacity(n_cells);
for i in 0..n_a {
cell_centres.push(Vector3::new(0.25 * lx, (i as f64 + 0.5) * dy_a, 0.0));
cell_volumes.push(0.5 * lx * dy_a * depth);
}
for j in 0..n_b {
cell_centres.push(Vector3::new(0.75 * lx, (j as f64 + 0.5) * dy_b, 0.0));
cell_volumes.push(0.5 * lx * dy_b * depth);
}
let p0 = 0;
let p1 = n_a;
let p2 = n_a + n_b;
let p3 = n_a + 2 * n_b;
let n_faces = 2 * n_a + 2 * n_b;
let mut owner = vec![0usize; n_faces];
let mut face_centres = vec![Vector3::ZERO; n_faces];
let mut face_area_vectors = vec![Vector3::ZERO; n_faces];
for i in 0..n_a {
let yc = (i as f64 + 0.5) * dy_a;
owner[p0 + i] = i;
face_centres[p0 + i] = Vector3::new(0.5 * lx, yc, 0.0);
face_area_vectors[p0 + i] = Vector3::new(a_area, 0.0, 0.0);
owner[p3 + i] = i;
face_centres[p3 + i] = Vector3::new(0.0, yc, 0.0);
face_area_vectors[p3 + i] = Vector3::new(-a_area, 0.0, 0.0);
}
for j in 0..n_b {
let yc = (j as f64 + 0.5) * dy_b;
let cell = n_a + j;
owner[p1 + j] = cell;
face_centres[p1 + j] = Vector3::new(0.5 * lx, yc, 0.0);
face_area_vectors[p1 + j] = Vector3::new(-b_area, 0.0, 0.0);
owner[p2 + j] = cell;
face_centres[p2 + j] = Vector3::new(lx, yc, 0.0);
face_area_vectors[p2 + j] = Vector3::new(b_area, 0.0, 0.0);
}
let a_spans: Vec<(f64, f64)> = (0..n_a)
.map(|i| (i as f64 * dy_a, (i as f64 + 1.0) * dy_a))
.collect();
let b_spans: Vec<(f64, f64)> = (0..n_b)
.map(|j| (j as f64 * dy_b, (j as f64 + 1.0) * dy_b))
.collect();
let mid_overlaps = overlap_weights_1d(&a_spans, &b_spans, depth);
let mut ami_couplings = Vec::with_capacity(n_a + n_b);
for (i, ov_row) in mid_overlaps.iter().enumerate() {
let weights = ov_row
.iter()
.map(|o| AmiWeight {
source_face: p1 + o.source,
source_cell: n_a + o.source,
weight: o.weight,
overlap_area: o.overlap_area,
})
.collect();
ami_couplings.push(AmiCoupling {
target_face: p0 + i,
target_cell: i,
target_patch: 0,
source_patch: 1,
local: i,
weights,
});
}
let wrap_overlaps = overlap_weights_1d(&b_spans, &a_spans, depth);
for (j, ov_row) in wrap_overlaps.iter().enumerate() {
let weights = ov_row
.iter()
.map(|o| AmiWeight {
source_face: p3 + o.source,
source_cell: o.source,
weight: o.weight,
overlap_area: o.overlap_area,
})
.collect();
ami_couplings.push(AmiCoupling {
target_face: p2 + j,
target_cell: n_a + j,
target_patch: 2,
source_patch: 3,
local: j,
weights,
});
}
let patches = vec![
BoundaryPatch::new("A_right", p0, n_a, PatchKind::CyclicAmi),
BoundaryPatch::new("B_left", p1, n_b, PatchKind::CyclicAmi),
BoundaryPatch::new("B_right", p2, n_b, PatchKind::CyclicAmi),
BoundaryPatch::new("A_left", p3, n_a, PatchKind::CyclicAmi),
];
FvMeshBuilder::new()
.n_cells(n_cells)
.n_internal_faces(0)
.owner(owner)
.neighbour(vec![])
.patches(patches)
.ami_couplings(ami_couplings)
.cell_volumes(cell_volumes)
.cell_centres(cell_centres)
.face_area_vectors(face_area_vectors)
.face_centres(face_centres)
.build()
.expect("periodic_ring_ami builds a consistent mesh")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vv_overlap_weights_coarse_over_fine() {
let src = [(0.0, 0.25), (0.25, 0.5), (0.5, 0.75), (0.75, 1.0)];
let w = overlap_weights_1d(&[(0.0, 1.0)], &src, 2.0);
assert_eq!(w[0].len(), 4);
let mut sum = 0.0;
for (k, o) in w[0].iter().enumerate() {
assert_eq!(o.source, k);
assert!(
(o.overlap_area - 0.5).abs() < 1e-15,
"area {}",
o.overlap_area
);
assert!((o.weight - 0.25).abs() < 1e-15, "weight {}", o.weight);
sum += o.weight;
}
assert!((sum - 1.0).abs() < 1e-15, "Σw = {sum}");
}
#[test]
fn vv_overlap_weights_partial() {
let w = overlap_weights_1d(&[(0.2, 0.8)], &[(0.0, 0.5), (0.5, 1.0)], 1.0);
assert_eq!(w[0].len(), 2);
assert!((w[0][0].overlap_area - 0.3).abs() < 1e-15);
assert!((w[0][1].overlap_area - 0.3).abs() < 1e-15);
assert!((w[0][0].weight - 0.5).abs() < 1e-15);
assert!((w[0][1].weight - 0.5).abs() < 1e-15);
let s: f64 = w[0].iter().map(|o| o.weight).sum();
assert!((s - 1.0).abs() < 1e-15);
}
#[test]
fn overlap_disjoint_is_empty() {
let w = overlap_weights_1d(&[(0.0, 0.5)], &[(0.6, 1.0)], 1.0);
assert!(w[0].is_empty());
}
#[test]
fn vv_ring_topology_and_weight_sum() {
let m = FvMesh::periodic_ring_ami(2, 4, 1.0, 1.0, 1.0);
assert_eq!(m.n_cells, 6);
assert_eq!(m.n_internal_faces, 0);
assert_eq!(m.patches.len(), 4);
for p in &m.patches {
assert_eq!(p.kind, PatchKind::CyclicAmi);
}
assert_eq!(m.ami_couplings.len(), 6);
for cc in &m.ami_couplings {
assert!(
(cc.weight_sum() - 1.0).abs() < 1e-14,
"target {} weight_sum = {}",
cc.target_face,
cc.weight_sum()
);
}
let mid0 = &m.ami_couplings[0];
assert_eq!(mid0.target_cell, 0);
assert_eq!(mid0.weights.len(), 2);
for w in &mid0.weights {
assert!((w.weight - 0.5).abs() < 1e-14);
}
assert_eq!(m.n_ami_faces(), 8);
assert_eq!(m.ami_ldu_start(), 0); }
#[test]
fn vv_ring_matching_is_identity() {
let m = FvMesh::periodic_ring_ami(3, 3, 1.0, 1.0, 1.0);
assert_eq!(m.ami_couplings.len(), 6);
for cc in &m.ami_couplings {
assert_eq!(
cc.weights.len(),
1,
"matching target should have one source"
);
assert!((cc.weights[0].weight - 1.0).abs() < 1e-14);
}
}
}