pub mod bloch_wave;
pub mod boundary;
mod coeffs;
pub mod conformal;
pub mod cylindrical;
pub mod dispersive;
pub mod dump;
pub mod edge_dft;
mod edge_material;
mod excite;
mod fields;
mod grid;
mod kernel;
pub mod lumped;
mod material;
pub mod medium;
mod multigrid;
pub mod oblique_wire;
mod operator;
pub mod planewave;
mod pool;
pub mod port;
mod probe;
mod rows;
pub(crate) mod run;
pub mod sheet;
mod slab;
pub mod wire;
use core::marker::PhantomData;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
pub use bloch_wave::{PeriodicPlaneWave, PeriodicPlaneWaveSpec, WavePart};
pub use boundary::periodic::BlochPair;
pub use coeffs::{
Coefficients, FaceCondition, FaceConditions, TimeStep, courant_limit, dual_edge_lr,
primary_edge_cg,
};
pub use conformal::{
ConformalEdge, ConformalFace, ConformalOptions, ConformalPec, ConformalReport, ConformalSheets,
MergedFaces, SheetCutEdge, SheetCutFace, SheetRegion,
};
pub use dispersive::{Dispersive, EdgePole, Pole};
pub use dump::{
DumpPosition, DumpQuantity, DumpRegion, DumpSink, FieldSampler, FreqDump, MemorySink, Schedule,
Snapshot, TimeDump, VtrSeries,
};
pub use edge_dft::{CellBox, EdgeDft};
pub use edge_material::EdgeMaterials;
pub use excite::{Amplitude, EdgeSource, SourceKind};
pub use fields::{FieldArrays, Fields};
pub use grid::{Coordinates, Grid};
pub use lumped::{EdgeValues, LumpedBox, LumpedElement, Topology, fold_parallel};
pub use material::{CellMaterials, Material};
pub use medium::Medium;
pub use oblique_wire::{ObliqueWire, ObliqueWires};
pub use operator::{Hooks, Operator, OperatorHandle, StepContext};
pub use planewave::{PlaneWave, PlaneWaveSpec};
pub use port::LumpedPort;
pub use probe::{FieldProbe, Probe, ProbeQuantity};
pub use run::{Progress, RunLimits, RunOutcome, StopReason};
pub use sheet::{ConductingSheet, SheetEdge};
pub use slab::{SlabContext, SlabHooks, SlabMut, SlabPartition, SlabRef};
pub use wire::ThinWire;
use crate::axis::Axis;
use crate::fdtd::Face;
use crate::{Error, Result};
use boundary::periodic::Periodic;
use fields::{FieldStore, Layout, read, write};
use pool::{Gap, Gaps, Plan, Pool, Shared};
#[derive(Debug, Clone)]
pub struct EngineBuilder {
grid: Grid,
materials: Option<Materials>,
faces: FaceConditions,
timestep: TimeStep,
threads: usize,
}
#[derive(Debug, Clone)]
enum Materials {
Cells(CellMaterials),
Edges(Box<EdgeMaterials>),
}
impl EngineBuilder {
pub fn materials(mut self, m: CellMaterials) -> Self {
self.materials = Some(Materials::Cells(m));
self
}
pub fn edge_materials(mut self, m: EdgeMaterials) -> Self {
self.materials = Some(Materials::Edges(Box::new(m)));
self
}
pub fn face(mut self, face: Face, c: FaceCondition) -> Self {
self.faces[face] = c;
self
}
pub fn faces(mut self, faces: impl Into<FaceConditions>) -> Self {
self.faces = faces.into();
self
}
pub fn timestep(mut self, t: TimeStep) -> Self {
self.timestep = t;
self
}
pub fn threads(mut self, n: usize) -> Self {
self.threads = n;
self
}
pub fn build(self) -> Result<Engine> {
let grid = self.grid;
let faces = cylindrical::effective_faces(&grid, &self.faces);
let periodic = Periodic::from_faces(&faces, grid.nodes())?;
let axis = cylindrical::AxisMerge::new(&grid);
let multigrid = multigrid::Multigrid::new(&grid);
let dt = self.timestep.resolve(&grid)?;
let coeffs = match &self.materials {
Some(Materials::Cells(m)) => Coefficients::compute(&grid, m, &self.faces, dt)?,
Some(Materials::Edges(m)) => Coefficients::from_edges(&grid, m, &self.faces, dt)?,
None => {
Coefficients::from_edges(&grid, &EdgeMaterials::vacuum(&grid), &self.faces, dt)?
}
};
let threads = match self.threads {
0 => default_threads(grid.num_cells()),
n => n,
};
let layout = Layout::new(grid.nodes(), threads);
let threads = layout.slabs();
let shared = Arc::new(Shared::new(FieldStore::new(layout), coeffs, threads));
let pool = if threads > 1 {
Some(Pool::spawn(&shared).map_err(|e| Error::Io(format!("spawning workers: {e}")))?)
} else {
None
};
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
Ok(Engine {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
grid,
dt,
step: 0,
shared,
ops: Vec::new(),
periodic,
axis,
multigrid,
pool,
})
}
}
const MIN_CELLS_PER_THREAD: usize = 8192;
pub fn default_threads(cells: usize) -> usize {
let avail = std::thread::available_parallelism().map_or(1, |n| n.get());
let cores = (avail / smt_width()).max(1);
cores.min(cells / MIN_CELLS_PER_THREAD).max(1)
}
fn smt_width() -> usize {
use std::sync::OnceLock;
static W: OnceLock<usize> = OnceLock::new();
*W.get_or_init(|| {
let mut cores = std::collections::HashSet::new();
let mut cpus = 0usize;
for cpu in 0.. {
let path = format!("/sys/devices/system/cpu/cpu{cpu}/topology/thread_siblings_list");
match std::fs::read_to_string(path) {
Ok(s) => {
cpus += 1;
cores.insert(s.trim().to_owned());
}
Err(_) => break,
}
}
if cores.is_empty() {
1
} else {
(cpus / cores.len()).max(1)
}
})
}
pub struct Engine {
id: u64,
grid: Grid,
dt: f64,
step: u64,
shared: Arc<Shared>,
ops: Vec<Registered>,
periodic: Option<Periodic>,
axis: Option<cylindrical::AxisMerge>,
multigrid: Option<multigrid::Multigrid>,
pool: Option<Pool>,
}
struct Registered {
op: Box<dyn Operator>,
hooks: Hooks,
}
impl core::fmt::Debug for Engine {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Engine")
.field("nodes", &self.grid.nodes())
.field("dt", &self.dt)
.field("step", &self.step)
.field("threads", &self.threads())
.field("operators", &self.ops.len())
.finish_non_exhaustive()
}
}
impl Drop for Engine {
fn drop(&mut self) {
if let Some(p) = &mut self.pool {
p.shutdown(&self.shared);
}
}
}
impl Engine {
pub fn builder(grid: Grid) -> EngineBuilder {
EngineBuilder {
grid,
materials: None,
faces: FaceConditions::default(),
timestep: TimeStep::default(),
threads: 0,
}
}
pub fn grid(&self) -> &Grid {
&self.grid
}
pub fn dt(&self) -> f64 {
self.dt
}
pub fn timestep(&self) -> u64 {
self.step
}
pub fn time(&self) -> f64 {
self.step as f64 * self.dt
}
pub fn threads(&self) -> usize {
self.shared.store.layout.slabs()
}
pub fn add_operator<T: Operator>(&mut self, mut op: T) -> OperatorHandle<T> {
let hooks = op.hooks();
let part = SlabPartition {
grid: &self.grid,
dt: self.dt,
starts: &self.shared.store.layout.slab_start,
};
if let Some(s) = op.slab_hooks(&part) {
write(&self.shared.slab_ops).0.push(s);
}
self.ops.push(Registered {
op: Box::new(op),
hooks,
});
OperatorHandle {
engine: self.id,
index: self.ops.len() - 1,
_t: PhantomData,
}
}
fn op_index<T>(&self, h: &OperatorHandle<T>) -> usize {
assert_eq!(h.engine, self.id, "operator handle from another engine");
h.index
}
pub fn operator<T: Operator>(&self, h: &OperatorHandle<T>) -> &T {
let op: &dyn core::any::Any = &*self.ops[self.op_index(h)].op;
op.downcast_ref().expect("operator type matches its handle")
}
pub fn operator_mut<T: Operator>(&mut self, h: &OperatorHandle<T>) -> &mut T {
let i = self.op_index(h);
let op: &mut dyn core::any::Any = &mut *self.ops[i].op;
op.downcast_mut().expect("operator type matches its handle")
}
pub fn coefficients(&self) -> impl core::ops::Deref<Target = Coefficients> + '_ {
read(&self.shared.coeffs)
}
pub fn coefficients_mut(&mut self) -> impl core::ops::DerefMut<Target = Coefficients> + '_ {
self.shared.coeffs_changed();
write(&self.shared.coeffs)
}
pub fn fields_mut(&mut self) -> Fields<'_> {
self.shared.store.lock()
}
pub fn u(&self, axis: Axis, node: [usize; 3]) -> f32 {
let (s, o) = self.shared.store.layout.locate(node);
read(&self.shared.store.u[s])[axis.index()][o]
}
pub fn i(&self, axis: Axis, node: [usize; 3]) -> f32 {
let (s, o) = self.shared.store.layout.locate(node);
read(&self.shared.store.i[s])[axis.index()][o]
}
pub fn u_component(&self, axis: Axis) -> Vec<f32> {
self.shared
.store
.u
.iter()
.flat_map(|b| read(b)[axis.index()].clone())
.collect()
}
pub fn i_component(&self, axis: Axis) -> Vec<f32> {
self.shared
.store
.i
.iter()
.flat_map(|b| read(b)[axis.index()].clone())
.collect()
}
pub fn energy(&self) -> f64 {
self.shared.current_energy(self.dt) + self.operator_energy()
}
pub fn operator_energy(&self) -> f64 {
self.ops.iter().map(|r| r.op.energy()).sum()
}
pub fn energy_staggered(&self) -> f64 {
let co = read(&self.shared.coeffs);
let st = &self.shared.store;
let p = st.layout.plane;
let mut w = 0.0f64;
for (blocks, lane) in [(&st.u, 0), (&st.i, 6)] {
for axis in 0..3 {
let (a, b) = (&co.data[lane + axis], &co.data[lane + 3 + axis]);
for (s, blk) in blocks.iter().enumerate() {
let off = st.layout.planes(s).0 * p;
let blk = read(blk);
let v = &blk[axis];
let a = &a[off..off + v.len()];
let b = &b[off..off + v.len()];
for ((&x, &ca), &cb) in v.iter().zip(a).zip(b) {
let c = Coefficients::capacitance(ca, cb, self.dt);
w += 0.5 * c * (x as f64) * (x as f64);
}
}
}
}
w
}
pub fn step(&mut self) {
self.run(1);
}
pub fn step_with_energy(&mut self) -> f64 {
let w_ops = self.operator_energy();
self.advance(1, true);
self.shared.recorded_energy(self.dt) + w_ops
}
pub fn run(&mut self, n: u64) {
self.advance(n, false);
}
fn advance(&mut self, n: u64, energy_first: bool) {
let mask = self.ops.iter().fold(Hooks::NONE, |m, r| m | r.hooks);
let plan = Plan {
steps: n,
step0: self.step,
dt: self.dt,
energy_first,
gaps: Gaps {
a: mask.intersects(Hooks::PRE_H | Hooks::POST_E)
|| self.periodic.is_some()
|| self.axis.is_some()
|| self.multigrid.is_some(),
b: mask.intersects(Hooks::POST_H | Hooks::PRE_E) || self.multigrid.is_some(),
},
};
let Self {
grid,
dt,
shared,
ops,
periodic,
axis,
multigrid,
pool,
..
} = self;
let post_e_ops = mask.contains(Hooks::POST_E);
let mid_ops = mask.intersects(Hooks::POST_H | Hooks::PRE_E);
let mut hook = |g: Gap| {
let (t, which): (u64, &[Hooks]) = match g {
Gap::PreH(t) => (t, &[Hooks::PRE_H]),
Gap::Mid(t) => (t, &[Hooks::POST_H, Hooks::PRE_E]),
Gap::PostE(t) => (t, &[Hooks::POST_E]),
};
let co = read(&shared.coeffs);
let ctx = StepContext {
step: plan.step0 + t,
dt: *dt,
grid,
coefficients: &co,
};
let mut f = shared.store.lock();
let post_e = matches!(g, Gap::PostE(_));
let periodic = periodic.as_ref().filter(|_| post_e);
let axis = axis.as_ref().filter(|_| post_e);
let mid = matches!(g, Gap::Mid(_));
let mg_e = multigrid.as_ref().filter(|_| post_e);
let mg_h = multigrid.as_ref().filter(|_| mid);
if let Some(p) = periodic {
p.merge(&mut f, &co);
}
if let Some(a) = axis {
a.merge(&mut f, &co);
}
if let Some(m) = mg_e {
m.merge_e(&mut f.u, &co);
}
if let Some(m) = mg_h {
m.merge_h(&mut f.i, &co);
}
for &w in which {
for r in ops.iter_mut().filter(|r| r.hooks.contains(w)) {
match w {
Hooks::PRE_H => r.op.pre_h(&mut f, &ctx),
Hooks::POST_H => r.op.post_h(&mut f, &ctx),
Hooks::PRE_E => r.op.pre_e(&mut f, &ctx),
_ => r.op.post_e(&mut f, &ctx),
}
}
}
if post_e_ops {
if let Some(p) = periodic {
p.merge(&mut f, &co);
}
if let Some(a) = axis {
a.merge(&mut f, &co);
}
if let Some(m) = mg_e {
m.merge_e(&mut f.u, &co);
}
}
if mid_ops && let Some(m) = mg_h {
m.merge_h(&mut f.i, &co);
}
};
pool::drive(pool.as_mut(), shared, &plan, &mut hook);
self.step += n;
}
}