use std::path::Path;
use crate::compile::sar::SarKind;
use crate::engine::{FieldSampler, FreqDump, RunOutcome, Snapshot, VtrSeries};
use crate::post::sar::{SarField, SarVolume};
use crate::post::{Complex64, dft};
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ProbeKind {
Voltage,
Current,
EField,
HField,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ProbeValues {
Scalar(Vec<f64>),
Vector(Vec<[f64; 3]>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct ProbeResult {
pub(crate) name: String,
pub(crate) id: u32,
pub(crate) number: u32,
pub(crate) kind: ProbeKind,
pub(crate) frequencies: Vec<f64>,
pub(crate) dt: f64,
pub(crate) times: Vec<f64>,
pub(crate) values: ProbeValues,
pub(crate) imag: Option<ProbeValues>,
}
impl ProbeResult {
pub fn name(&self) -> &str {
&self.name
}
pub fn id(&self) -> u32 {
self.id
}
pub fn number(&self) -> u32 {
self.number
}
pub fn kind(&self) -> ProbeKind {
self.kind
}
pub fn frequencies(&self) -> &[f64] {
&self.frequencies
}
pub fn dt(&self) -> f64 {
self.dt
}
pub fn times(&self) -> &[f64] {
&self.times
}
pub fn values(&self) -> &ProbeValues {
&self.values
}
pub fn scalar(&self) -> Option<&[f64]> {
match &self.values {
ProbeValues::Scalar(v) => Some(v),
ProbeValues::Vector(_) => None,
}
}
pub fn vector(&self) -> Option<&[[f64; 3]]> {
match &self.values {
ProbeValues::Vector(v) => Some(v),
ProbeValues::Scalar(_) => None,
}
}
pub fn imag(&self) -> Option<&ProbeValues> {
self.imag.as_ref()
}
fn t0(&self) -> f64 {
self.times.first().copied().unwrap_or(0.0)
}
fn combine(re: Vec<Complex64>, im: Vec<Complex64>) -> Vec<Complex64> {
re.into_iter()
.zip(im)
.map(|(r, i)| r + Complex64::I * i)
.collect()
}
pub fn dft(&self, freqs: &[f64]) -> Result<Vec<Complex64>> {
let v = self.scalar().ok_or_else(|| {
Error::Invalid(format!("probe {:?} records a field vector", self.name))
})?;
if v.is_empty() {
return Err(Error::Invalid(format!(
"probe {:?} has no samples",
self.name
)));
}
let re = dft(self.t0(), self.dt, v, freqs);
Ok(match &self.imag {
Some(ProbeValues::Scalar(im)) => Self::combine(re, dft(self.t0(), self.dt, im, freqs)),
_ => re,
})
}
pub fn dft_vector(&self, freqs: &[f64]) -> Result<Vec<[Complex64; 3]>> {
let v = self
.vector()
.ok_or_else(|| Error::Invalid(format!("probe {:?} records a scalar", self.name)))?;
if v.is_empty() {
return Err(Error::Invalid(format!(
"probe {:?} has no samples",
self.name
)));
}
let series = |v: &[[f64; 3]], c: usize| {
let x: Vec<f64> = v.iter().map(|s| s[c]).collect();
dft(self.t0(), self.dt, &x, freqs)
};
let comp: Vec<Vec<Complex64>> = (0..3)
.map(|c| match &self.imag {
Some(ProbeValues::Vector(im)) => Self::combine(series(v, c), series(im, c)),
_ => series(v, c),
})
.collect();
Ok((0..freqs.len())
.map(|k| [comp[0][k], comp[1][k], comp[2][k]])
.collect())
}
pub fn spectrum(&self) -> Result<Vec<Complex64>> {
self.dft(&self.frequencies)
}
}
#[derive(Debug, Clone)]
pub enum DumpData {
Time {
sampler: FieldSampler,
snapshots: Vec<Snapshot>,
},
Freq(FreqDump),
}
#[derive(Debug, Clone)]
pub struct DumpResult {
pub(crate) name: String,
pub(crate) id: u32,
pub(crate) file_type: csx::properties::DumpFileType,
pub(crate) data: DumpData,
pub(crate) imag: Option<Vec<Snapshot>>,
}
impl DumpResult {
pub fn name(&self) -> &str {
&self.name
}
pub fn id(&self) -> u32 {
self.id
}
pub fn file_type(&self) -> csx::properties::DumpFileType {
self.file_type
}
pub fn data(&self) -> &DumpData {
&self.data
}
pub fn sampler(&self) -> &FieldSampler {
match &self.data {
DumpData::Time { sampler, .. } => sampler,
DumpData::Freq(f) => f.sampler(),
}
}
pub fn snapshots(&self) -> Option<&[Snapshot]> {
match &self.data {
DumpData::Time { snapshots, .. } => Some(snapshots),
DumpData::Freq(_) => None,
}
}
pub fn imag_snapshots(&self) -> Option<&[Snapshot]> {
self.imag.as_deref()
}
pub fn freq(&self) -> Option<&FreqDump> {
match &self.data {
DumpData::Freq(f) => Some(f),
DumpData::Time { .. } => None,
}
}
pub fn write_vtr(&self, dir: impl AsRef<Path>, basename: &str) -> Result<()> {
match &self.data {
DumpData::Time { sampler, snapshots } => {
use crate::engine::DumpSink;
let mut series = VtrSeries::new(dir.as_ref(), basename);
for s in snapshots {
series.snapshot(sampler, s.clone())?;
}
Ok(())
}
DumpData::Freq(f) => f.write_vtr(dir, basename),
}
}
#[cfg(feature = "hdf5")]
pub fn write_h5(&self, path: impl AsRef<Path>) -> Result<()> {
crate::io::h5::write_dump(path, self, &crate::io::h5::H5Options::default())
}
}
#[derive(Debug, Clone)]
pub struct SarResult {
pub(crate) name: String,
pub(crate) id: u32,
pub(crate) kind: SarKind,
pub(crate) file_type: csx::properties::DumpFileType,
pub(crate) frequencies: Vec<f64>,
pub(crate) volumes: Vec<SarVolume>,
}
impl SarResult {
pub fn name(&self) -> &str {
&self.name
}
pub fn id(&self) -> u32 {
self.id
}
pub fn kind(&self) -> SarKind {
self.kind
}
pub fn file_type(&self) -> csx::properties::DumpFileType {
self.file_type
}
pub fn frequencies(&self) -> &[f64] {
&self.frequencies
}
pub fn volume(&self, k: usize) -> &SarVolume {
&self.volumes[k]
}
pub fn volumes(&self) -> &[SarVolume] {
&self.volumes
}
pub fn field(&self, k: usize) -> Result<SarField> {
let v = &self.volumes[k];
match self.kind {
SarKind::Local | SarKind::Raw => Ok(v.local()),
SarKind::Averaged(m) => v.averaged(m),
}
}
}
#[derive(Debug, Clone)]
pub struct Results {
pub(crate) outcome: Option<RunOutcome>,
pub(crate) dt: f64,
pub(crate) probes: Vec<ProbeResult>,
pub(crate) lumped: Vec<ProbeResult>,
pub(crate) dumps: Vec<DumpResult>,
pub(crate) sar: Vec<SarResult>,
pub(crate) incident: Vec<IncidentWave>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct IncidentWave {
pub(crate) name: String,
pub(crate) dt: f64,
pub(crate) k_t: [f64; 3],
pub(crate) e: Vec<[Complex64; 3]>,
}
impl IncidentWave {
pub fn name(&self) -> &str {
&self.name
}
pub fn wavevector(&self) -> [f64; 3] {
self.k_t
}
pub fn times(&self) -> Vec<f64> {
(1..=self.e.len()).map(|n| n as f64 * self.dt).collect()
}
pub fn values(&self) -> &[[Complex64; 3]] {
&self.e
}
pub fn dft(&self, freqs: &[f64]) -> Vec<[Complex64; 3]> {
let comp: Vec<Vec<Complex64>> = (0..3)
.map(|c| {
let re: Vec<f64> = self.e.iter().map(|v| v[c].re).collect();
let im: Vec<f64> = self.e.iter().map(|v| v[c].im).collect();
ProbeResult::combine(
dft(self.dt, self.dt, &re, freqs),
dft(self.dt, self.dt, &im, freqs),
)
})
.collect();
(0..freqs.len())
.map(|k| [comp[0][k], comp[1][k], comp[2][k]])
.collect()
}
}
impl Results {
pub fn outcome(&self) -> Option<&RunOutcome> {
self.outcome.as_ref()
}
pub fn dt(&self) -> f64 {
self.dt
}
pub fn probes(&self) -> &[ProbeResult] {
&self.probes
}
pub fn probe(&self, name: &str) -> Option<&ProbeResult> {
self.probes.iter().find(|p| p.name == name)
}
pub fn probe_by_id(&self, id: csx::PropertyId) -> Option<&ProbeResult> {
self.probes.iter().find(|p| p.id == id.get())
}
pub fn lumped_current(&self, name: &str) -> Option<&ProbeResult> {
self.lumped.iter().find(|p| p.name == name)
}
pub fn lumped_currents(&self) -> &[ProbeResult] {
&self.lumped
}
pub fn dumps(&self) -> &[DumpResult] {
&self.dumps
}
pub fn dump(&self, name: &str) -> Option<&DumpResult> {
self.dumps.iter().find(|d| d.name == name)
}
pub fn dump_by_id(&self, id: csx::PropertyId) -> Option<&DumpResult> {
self.dumps.iter().find(|d| d.id == id.get())
}
pub fn sar_dumps(&self) -> &[SarResult] {
&self.sar
}
pub fn sar(&self, name: &str) -> Option<&SarResult> {
self.sar.iter().find(|d| d.name == name)
}
pub fn is_bloch(&self) -> bool {
self.probes
.iter()
.chain(&self.lumped)
.any(|p| p.imag.is_some())
|| !self.incident.is_empty()
}
pub fn incident_waves(&self) -> &[IncidentWave] {
&self.incident
}
pub fn incident(&self, name: &str) -> Option<&IncidentWave> {
self.incident.iter().find(|w| w.name == name)
}
}