use super::{
enforce_slab_face_boundaries_in_chain, enforce_slab_face_boundaries_on_rank,
multigrid_decomposed::{
multigrid_slab_bounds, project_pressure_multigrid_banded_on_rank, HALO,
},
project_pressure_decomposed_on_rank, project_pressure_slab_local_on_rank, slab_bounds,
write_band_back, HaloSchedule, MacGrid, SlabFaceConditions, SlabFaces, SlabSocketTransport,
SlabStorage, SocketTransport,
};
use crate::cfd_solver::{PressureSolver, PressureSolverError};
use crate::math::Fix128;
use std::io;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex, PoisonError};
#[derive(Clone)]
pub(super) struct MemoryLink(Arc<Mutex<LinkEnd>>);
struct LinkEnd {
tx: Sender<Vec<u8>>,
rx: Receiver<Vec<u8>>,
pending: Vec<u8>,
at: usize,
traffic: Arc<AtomicUsize>,
}
impl MemoryLink {
fn pair(traffic: &Arc<AtomicUsize>) -> (Self, Self) {
let (tx_ab, rx_ab) = channel();
let (tx_ba, rx_ba) = channel();
let end = |tx, rx| {
Self(Arc::new(Mutex::new(LinkEnd {
tx,
rx,
pending: Vec::new(),
at: 0,
traffic: Arc::clone(traffic),
})))
};
(end(tx_ab, rx_ba), end(tx_ba, rx_ab))
}
}
impl io::Read for MemoryLink {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let mut end = self.0.lock().unwrap_or_else(PoisonError::into_inner);
if end.at == end.pending.len() {
match end.rx.recv() {
Ok(message) => {
end.pending = message;
end.at = 0;
}
Err(_) => return Ok(0), }
}
let n = buf.len().min(end.pending.len() - end.at);
let at = end.at;
buf[..n].copy_from_slice(&end.pending[at..at + n]);
end.at += n;
Ok(n)
}
}
impl io::Write for MemoryLink {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let end = self.0.lock().unwrap_or_else(PoisonError::into_inner);
end.tx
.send(buf.to_vec())
.map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "the peer rank has gone"))?;
end.traffic.fetch_add(buf.len(), Ordering::Relaxed);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
fn mesh(ranks: usize, traffic: &Arc<AtomicUsize>) -> Vec<Vec<Option<MemoryLink>>> {
let mut links: Vec<Vec<Option<MemoryLink>>> = (0..ranks)
.map(|_| (0..ranks).map(|_| None).collect())
.collect();
for (a, b) in (0..ranks).flat_map(|a| (a + 1..ranks).map(move |b| (a, b))) {
let (to_b, to_a) = MemoryLink::pair(traffic);
links[a][b] = Some(to_b);
links[b][a] = Some(to_a);
}
links
}
fn band_from_field(
bounds: &[(usize, usize)],
rank: usize,
nz: usize,
plane: usize,
halo: usize,
field: Option<&[Fix128]>,
) -> SlabStorage {
let mut band = SlabStorage::for_slab(plane, nz, bounds[rank], halo);
if let Some(f) = field {
let (lo, hi) = band.resident();
for k in lo..hi {
band.layer_mut(k)
.expect("a layer inside the band this slab just reported")
.copy_from_slice(&f[k * plane..(k + 1) * plane]);
}
}
band
}
fn run_ranks<R, F>(ranks: usize, rank_main: F) -> (Vec<R>, usize)
where
R: Send,
F: Fn(usize, Vec<Option<MemoryLink>>) -> R + Sync,
{
let rank_main = &rank_main;
let traffic = Arc::new(AtomicUsize::new(0));
let out = std::thread::scope(|scope| {
let handles: Vec<_> = mesh(ranks, &traffic)
.into_iter()
.enumerate()
.map(|(rank, links)| scope.spawn(move || rank_main(rank, links)))
.collect();
let mut out = Vec::with_capacity(ranks);
let mut failure = None;
for handle in handles {
match handle.join() {
Ok(r) => out.push(r),
Err(payload) => {
failure.get_or_insert(payload);
}
}
}
if let Some(payload) = failure {
std::panic::resume_unwind(payload);
}
out
});
(out, traffic.load(Ordering::Relaxed))
}
pub fn project_pressure_distributed(
grid: &mut MacGrid,
dt_s: Fix128,
density_kg_m3: Fix128,
solver: PressureSolver,
) -> Result<(), PressureSolverError> {
run(grid, dt_s, density_kg_m3, solver).map(|_| ())
}
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DistributedProjectionReport {
pub rank_bytes: Vec<usize>,
}
impl DistributedProjectionReport {
#[must_use]
pub fn max_rank_bytes(&self) -> Option<usize> {
self.rank_bytes.iter().copied().max()
}
#[must_use]
pub fn total_bytes(&self) -> Option<usize> {
(!self.rank_bytes.is_empty()).then(|| self.rank_bytes.iter().sum())
}
}
pub fn project_pressure_distributed_with_report(
grid: &mut MacGrid,
dt_s: Fix128,
density_kg_m3: Fix128,
solver: PressureSolver,
) -> Result<DistributedProjectionReport, PressureSolverError> {
run(grid, dt_s, density_kg_m3, solver).map(|ran| ran.report)
}
struct Ran {
report: DistributedProjectionReport,
wire_bytes: usize,
}
fn run(
grid: &mut MacGrid,
dt_s: Fix128,
density_kg_m3: Fix128,
solver: PressureSolver,
) -> Result<Ran, PressureSolverError> {
if dt_s.is_zero() {
return Err(PressureSolverError::ZeroTimeStep);
}
if density_kg_m3.is_zero() {
return Err(PressureSolverError::ZeroDensity);
}
if grid.dx.is_zero() {
return Err(PressureSolverError::ZeroSpacing);
}
match solver {
PressureSolver::DecomposedGs { sweeps: 0, .. }
| PressureSolver::BandedGs { sweeps: 0, .. }
| PressureSolver::DecomposedMultigrid { cycles: 0, .. } => {
Err(PressureSolverError::ZeroIterations)
}
PressureSolver::DecomposedGs { ranks: 0, .. }
| PressureSolver::BandedGs { ranks: 0, .. }
| PressureSolver::DecomposedMultigrid { ranks: 0, .. } => {
Err(PressureSolverError::ZeroRanks)
}
PressureSolver::DecomposedGs { ranks, sweeps } => {
Ok(decomposed_gs(grid, dt_s, density_kg_m3, sweeps, ranks))
}
PressureSolver::BandedGs { ranks, sweeps } => {
Ok(banded_gs(grid, dt_s, density_kg_m3, sweeps, ranks))
}
PressureSolver::DecomposedMultigrid { ranks, cycles } => {
let (nx, ny, nz) = (grid.nx, grid.ny, grid.nz);
let Some(bounds) = multigrid_slab_bounds(nx, ny, nz, ranks) else {
return Err(PressureSolverError::MultigridNeedsPowerOfTwoExtents {
extents: (nx, ny, nz),
});
};
Ok(banded_multigrid(grid, dt_s, density_kg_m3, cycles, &bounds))
}
_ => Err(PressureSolverError::NotDecomposed),
}
}
fn decomposed_gs(
grid: &mut MacGrid,
dt_s: Fix128,
density: Fix128,
sweeps: u32,
ranks: usize,
) -> Ran {
let start: &MacGrid = grid;
let n = start.nx * start.ny * start.nz;
let plane = start.nx * start.ny;
let (mut grids, wire_bytes) = run_ranks(ranks, |rank, links| {
let mut own = start.clone();
let mut transport = SocketTransport::new(rank, n, plane, links);
project_pressure_decomposed_on_rank(
&mut own,
dt_s,
density,
sweeps,
ranks,
HaloSchedule::EverySweep,
rank,
&mut transport,
);
own
});
*grid = grids.swap_remove(0);
Ran {
report: DistributedProjectionReport {
rank_bytes: Vec::new(),
},
wire_bytes,
}
}
fn banded_gs(grid: &mut MacGrid, dt_s: Fix128, density: Fix128, sweeps: u32, ranks: usize) -> Ran {
let (nz, plane) = (grid.nz, grid.nx * grid.ny);
let bounds: Vec<(usize, usize)> = (0..ranks).map(|r| slab_bounds(nz, ranks, r)).collect();
let start: &MacGrid = grid;
let (solved, wire_bytes) = run_ranks(ranks, |rank, links| {
let mut faces = SlabFaces::from_grid(start, bounds[rank]);
let conditions = SlabFaceConditions::from_grid(start, bounds[rank]);
let band = band_from_field(&bounds, rank, nz, plane, 1, Some(&start.pressure));
let mut transport = SlabSocketTransport::new(rank, plane, band, links);
enforce_slab_face_boundaries_on_rank(&mut faces, &conditions, ranks, rank, &mut transport);
let stencil = project_pressure_slab_local_on_rank(
&mut faces,
dt_s,
density,
sweeps,
ranks,
HaloSchedule::EverySweep,
rank,
&mut transport,
);
let held = faces.bytes().add(transport.slab().bytes()).add(stencil);
(faces, transport, held)
});
for (faces, transport, _) in &solved {
write_band_back(grid, faces, transport.slab());
}
Ran {
report: DistributedProjectionReport {
rank_bytes: solved.iter().map(|(_, _, held)| held.total()).collect(),
},
wire_bytes,
}
}
fn banded_multigrid(
grid: &mut MacGrid,
dt_s: Fix128,
density: Fix128,
cycles: u32,
bounds: &[(usize, usize)],
) -> Ran {
let (nz, plane) = (grid.nz, grid.nx * grid.ny);
let ranks = bounds.len();
let start: &MacGrid = grid;
let (solved, wire_bytes) = run_ranks(ranks, |rank, links| {
let mut faces = SlabFaces::from_grid(start, bounds[rank]);
let conditions = SlabFaceConditions::from_grid(start, bounds[rank]);
let (k0, _) = bounds[rank];
let mut channel = SlabSocketTransport::new(
rank,
plane,
SlabStorage::for_slab(plane, nz, (k0, k0), 0),
links.clone(),
);
enforce_slab_face_boundaries_in_chain(&mut faces, &conditions, bounds, rank, &mut channel);
let mut band = band_from_field(bounds, rank, nz, plane, HALO, Some(&start.pressure));
let stencil = project_pressure_multigrid_banded_on_rank(
&mut faces,
&mut band,
dt_s,
density,
cycles,
ranks,
HaloSchedule::EverySweep,
rank,
|level_bounds: &[(usize, usize)], nz, plane, halo, field: Option<&[Fix128]>| {
let slab = band_from_field(level_bounds, rank, nz, plane, halo, field);
SlabSocketTransport::new(rank, plane, slab, links.clone())
},
);
let held = faces.bytes().add(band.bytes()).add(stencil);
(faces, band, held)
});
for (faces, band, _) in &solved {
write_band_back(grid, faces, band);
}
Ran {
report: DistributedProjectionReport {
rank_bytes: solved.iter().map(|(_, _, held)| held.total()).collect(),
},
wire_bytes,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eulerian_grid::FaceBc;
fn outflow_grid(nx: usize, ny: usize, nz: usize) -> MacGrid {
let mut g = MacGrid::new(nx, ny, nz, Fix128::from_ratio(1, 8));
for (c, x) in g.u.iter_mut().enumerate() {
*x = Fix128::from_ratio(((c * 7) % 13) as i64 - 6, 4);
}
for (c, x) in g.v.iter_mut().enumerate() {
*x = Fix128::from_ratio(((c * 5) % 11) as i64 - 5, 4);
}
for (c, x) in g.w.iter_mut().enumerate() {
*x = Fix128::from_ratio(((c * 3) % 17) as i64 - 8, 4);
}
let inflow = FaceBc::Inflow {
normal_velocity: Fix128::from_ratio(3, 4),
};
for k in 0..nz {
for j in 0..ny {
g.set_u_bc(0, j, k, FaceBc::Outflow);
g.set_u_bc(1, j, k, inflow);
g.set_u_bc(nx, j, k, FaceBc::Outflow);
}
}
for j in 0..ny {
for i in 0..nx {
g.set_w_bc(i, j, 0, FaceBc::Outflow);
g.set_w_bc(i, j, 1, inflow);
g.set_w_bc(i, j, nz, FaceBc::Outflow);
}
}
g
}
#[test]
fn the_slab_solvers_impose_the_face_conditions_rank_by_rank() {
let (nx, ny) = (4usize, 4usize);
let plane_bytes = nx * ny * 16;
for nz in [8usize, 16] {
let base = outflow_grid(nx, ny, nz);
let mut want = base.clone();
want.enforce_face_boundaries();
assert_ne!(want.u, base.u, "the conditions must change the faces");
for ranks in [1usize, 2, 3, 4, 8, nz + 3] {
let owning = (0..ranks)
.filter(|&r| {
let (k0, k1) = slab_bounds(nz, ranks, r);
k0 != k1
})
.count();
let mut g = base.clone();
let ran = banded_gs(&mut g, Fix128::ZERO, Fix128::ONE, 1, ranks);
assert_eq!(
ran.wire_bytes,
(owning - 1) * plane_bytes,
"BandedGs nz {nz} over {ranks} ranks",
);
assert_eq!((&g.u, &g.v, &g.w), (&want.u, &want.v, &want.w));
assert_eq!(g.pressure, base.pressure);
let Some(bounds) = multigrid_slab_bounds(nx, ny, nz, ranks) else {
panic!("a power-of-two grid has a multigrid decomposition");
};
let owning = bounds.iter().filter(|&&(k0, k1)| k0 != k1).count();
let mut g = base.clone();
let ran = banded_multigrid(&mut g, Fix128::ZERO, Fix128::ONE, 1, &bounds);
assert_eq!(
ran.wire_bytes,
(owning - 1) * plane_bytes,
"DecomposedMultigrid nz {nz} over {ranks} ranks",
);
assert_eq!((&g.u, &g.v, &g.w), (&want.u, &want.v, &want.w));
}
}
}
}