mod map;
mod results;
#[cfg(feature = "xml")]
mod xml;
pub use results::{DumpData, DumpResult, ProbeKind, ProbeResult, ProbeValues, Results};
use crate::Result;
use crate::compile::{BoundaryHandles, CompileOptions, Compiled, ModelSources, NodeMap, compile};
use crate::engine::{
EdgeSource, Engine, FreqDump, Grid, LumpedElement, OperatorHandle, Progress, RunLimits,
TimeDump,
};
use crate::fdtd::Fdtd;
use map::{DumpOp, Mapped, ProbeOp};
#[derive(Debug)]
pub struct Simulation {
engine: Engine,
fdtd: Fdtd,
delta_unit: f64,
node_map: NodeMap,
boundaries: BoundaryHandles,
sources: ModelSources,
mapped: Mapped,
warnings: Vec<String>,
}
impl Simulation {
pub fn new(model: &csx::ContinuousStructure, fdtd: &Fdtd) -> Result<Self> {
Self::with_options(model, fdtd, &CompileOptions::default())
}
pub fn with_options(
model: &csx::ContinuousStructure,
fdtd: &Fdtd,
opts: &CompileOptions,
) -> Result<Self> {
let compiled = compile(model, fdtd, opts)?;
let (mut engine, boundaries) = compiled.build()?;
let mut warnings = compiled.warnings.clone();
if opts.mesh_check {
warnings.extend(mesh_warnings(model, fdtd, &compiled, opts)?);
}
let mut mapped = map::map_sources(
&mut engine,
&compiled.sources,
&compiled.node_map,
fdtd,
&mut warnings,
)?;
mapped.lumped_ids = mapped
.lumped
.iter()
.map(|(n, _)| model.property_id(n).map_or(u32::MAX, |p| p.get()))
.collect();
Ok(Self {
engine,
fdtd: fdtd.clone(),
delta_unit: compiled.delta_unit,
node_map: compiled.node_map,
boundaries,
sources: compiled.sources,
mapped,
warnings,
})
}
pub fn engine(&self) -> &Engine {
&self.engine
}
pub fn engine_mut(&mut self) -> &mut Engine {
&mut self.engine
}
pub fn into_engine(self) -> Engine {
self.engine
}
pub fn grid(&self) -> &Grid {
self.engine.grid()
}
pub fn delta_unit(&self) -> f64 {
self.delta_unit
}
pub fn fdtd(&self) -> &Fdtd {
&self.fdtd
}
pub fn node_map(&self) -> &NodeMap {
&self.node_map
}
pub fn boundaries(&self) -> &BoundaryHandles {
&self.boundaries
}
pub fn sources(&self) -> &ModelSources {
&self.sources
}
pub fn warnings(&self) -> &[String] {
&self.warnings
}
pub fn lumped_element(&self, name: &str) -> Option<&LumpedElement> {
let h = find(&self.mapped.lumped, name)?;
Some(self.engine.operator(&h))
}
pub fn source(&self, name: &str) -> Option<&EdgeSource> {
let h = find(&self.mapped.sources, name)?;
Some(self.engine.operator(&h))
}
pub fn time_dump(&self, name: &str) -> Option<&TimeDump> {
match self.mapped.dumps.iter().find(|d| d.name == name)?.op {
DumpOp::Time(h) => Some(self.engine.operator(&h)),
DumpOp::Freq(_) => None,
}
}
pub fn freq_dump(&self, name: &str) -> Option<&FreqDump> {
match self.mapped.dumps.iter().find(|d| d.name == name)?.op {
DumpOp::Freq(h) => Some(self.engine.operator(&h)),
DumpOp::Time(_) => None,
}
}
pub fn run_limits(&self) -> RunLimits {
let end = self
.fdtd
.get_waveform()
.map(|w| w.duration().unwrap_or(f64::INFINITY));
RunLimits::new(self.fdtd.timestep_limit(self.engine.dt()))
.end_criteria(self.fdtd.get_end_criteria().copied())
.excitation_end(end)
}
pub fn run(&mut self) -> Result<Results> {
let limits = self.run_limits();
let outcome = self.engine.run_until(&limits)?;
let mut r = self.results();
r.outcome = Some(outcome);
Ok(r)
}
pub fn run_with<F>(&mut self, progress: F) -> Result<Results>
where
F: FnMut(&Progress) -> core::ops::ControlFlow<()>,
{
let limits = self.run_limits();
let outcome = self.engine.run_until_with(&limits, progress)?;
let mut r = self.results();
r.outcome = Some(outcome);
Ok(r)
}
pub fn results(&self) -> Results {
let dt = self.engine.dt();
let probes = self
.mapped
.probes
.iter()
.map(|p| {
let (times, values) = match p.op {
ProbeOp::Scalar(h) => {
let op = self.engine.operator(&h);
(
op.times(),
results::ProbeValues::Scalar(op.values().to_vec()),
)
}
ProbeOp::Field(h) => {
let op = self.engine.operator(&h);
let w = p.weight;
let v = op.values().iter().map(|v| v.map(|x| x * w)).collect();
(op.times(), results::ProbeValues::Vector(v))
}
};
let eps = 1e-9 * dt;
let lo = times.partition_point(|&t| t < p.start_time - eps);
let hi = if p.stop_time > 0.0 {
times.partition_point(|&t| t <= p.stop_time + eps)
} else {
times.len()
}
.max(lo);
let values = match values {
results::ProbeValues::Scalar(v) => {
results::ProbeValues::Scalar(v[lo..hi].to_vec())
}
results::ProbeValues::Vector(v) => {
results::ProbeValues::Vector(v[lo..hi].to_vec())
}
};
ProbeResult {
name: p.name.clone(),
id: p.id,
number: p.number,
kind: p.kind,
frequencies: p.frequencies.clone(),
dt,
times: times[lo..hi].to_vec(),
values,
}
})
.collect();
let lumped = self
.mapped
.lumped
.iter()
.zip(&self.mapped.lumped_ids)
.map(|((name, h), &id)| {
let op = self.engine.operator(h);
ProbeResult {
name: name.clone(),
id,
number: 0,
kind: ProbeKind::Current,
frequencies: Vec::new(),
dt,
times: op.current_times(),
values: results::ProbeValues::Scalar(op.current().to_vec()),
}
})
.collect();
let dumps = self
.mapped
.dumps
.iter()
.map(|d| DumpResult {
name: d.name.clone(),
id: d.id,
data: match d.op {
DumpOp::Time(h) => {
let op = self.engine.operator(&h);
DumpData::Time {
sampler: op.sampler().clone(),
snapshots: op.sink().snapshots().to_vec(),
}
}
DumpOp::Freq(h) => DumpData::Freq(self.engine.operator(&h).clone()),
},
})
.collect();
Results {
outcome: None,
dt,
probes,
lumped,
dumps,
}
}
}
fn mesh_warnings(
model: &csx::ContinuousStructure,
fdtd: &Fdtd,
compiled: &Compiled,
opts: &CompileOptions,
) -> Result<Vec<String>> {
use crate::mesh::{CheckOptions, Severity, check_compiled};
let check = CheckOptions {
snap_tolerance: opts.snap_tolerance,
update_model: opts.update_model,
..CheckOptions::default()
};
let report = check_compiled(model, fdtd, compiled, &check)?;
Ok(report
.issues
.iter()
.filter(|i| i.severity >= Severity::Warning)
.filter(|i| {
!i.message
.starts_with(crate::mesh::CSX_DISCRETISATION_PREFIX)
})
.map(|i| format!("mesh {i}"))
.collect())
}
fn find<T>(list: &[(String, OperatorHandle<T>)], name: &str) -> Option<OperatorHandle<T>> {
list.iter().find(|(n, _)| n == name).map(|(_, h)| *h)
}