Files
fdtd-coremem/crates/coremem/src/meas.rs

674 lines
20 KiB
Rust

use crate::geom::{Meters, Region, Torus, WorldRegion};
use crate::real::{Real as _, ToFloat as _};
use crate::cross::vec::Vec3;
use crate::sim::AbstractSim;
use serde::{Serialize, Deserialize};
pub trait AbstractMeasurement<S>: Send + Sync {
fn eval(&self, state: &S) -> String;
fn key_value(&self, state: &S) -> Vec<Measurement>;
}
pub fn as_dyn_measurements<S, M: AbstractMeasurement<S>>(meas: &[M]) -> Vec<&dyn AbstractMeasurement<S>> {
meas.into_iter().map(|m| m as &dyn AbstractMeasurement<S>).collect()
}
/// combine several measurements
pub fn eval_multiple_kv<S>(state: &S, meas: &[&dyn AbstractMeasurement<S>]) -> Vec<Measurement>
meas.into_iter().flat_map(|m| m.key_value(state).into_iter()).collect()
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
pub enum MeasurementValue {
Field(Vec3<f32>),
Float(f32),
Int(u64),
Dim(Vec3u),
}
impl From<Vec3<f32>> for MeasurementValue {
fn from(v: Vec3<f32>) -> Self {
Self::Field(v)
}
}
impl From<f32> for MeasurementValue {
fn from(v: f32) -> Self {
Self::Float(v)
}
}
impl From<u64> for MeasurementValue {
fn from(v: u64) -> Self {
Self::Int(v)
}
}
impl From<Vec3u> for MeasurementValue {
fn from(v: Vec3u) -> Self {
Self::Dim(v)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Measurement {
name: String,
value: MeasurementValue,
/// e.g. "A" for Amps
unit: String,
}
impl Measurement {
fn new<T: Into<MeasurementValue>>(name: &str, value: T, unit: &str) -> Self {
Self {
name.to_owned(),
value: value.into(),
unit: unit.to_owned(),
}
}
fn new_unitless<T: Into<MeasurementValue>>(name: &str, value: T) -> Self {
Self::new(name, value, "")
}
}
enum SiScale {
Pico,
Nano,
Micro,
Milli,
Unit,
Kilo,
Mega,
Giga,
Terra,
}
impl SiScale {
fn for_value(v: f32) -> Self {
use SiScale::*;
match v {
v if v < 1e-12 => Unit,
v if v < 1e-9 => Pico,
v if v < 1e-6 => Nano,
v if v < 1e-3 => Micro,
v if v < 1e0 => Milli,
v if v < 1e3 => Unit,
v if v < 1e6 => Kilo,
v if v < 1e9 => Mega,
v if v < 1e12 => Giga,
v if v < 1e15 => Terra,
_ => Unit
}
}
/// return the numerical scale of this prefix.
/// e.g. `scale(&Pico) -> 1e-12
fn scale(&self) -> f32 {
use SiScale::*;
match *self {
Pico => 1e-12,
Nano => 1e-9,
Micro => 1e-6,
Milli => 1e-3,
Unit => 1.0,
Kilo => 1e3,
Mega => 1e6,
Giga => 1e9,
Terra => 1e12,
}
}
/// return the short string for this scale.
/// e.g. `shortcode(Pico) -> "p"`
fn shortcode(&self) -> &'static str {
use SiScale::*;
match *self {
Pico => "p",
Nano => "n",
Micro => "u",
Milli => "m",
Unit => "",
Kilo => "k",
Mega => "M",
Giga => "G",
Terra => "T",
}
}
/// format `v`, with the provided unit.
/// e.g. `format_short(1234, "A") -> "1.23 kA"
fn format_short(v: f32, unit: &str) -> String {
let si = SiScale::for_value(v);
let scaled = si.scale() * v;
format!("{:.2} {}{}", scaled, si.shortcode(), unit)
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Time;
impl<S: AbstractSim> AbstractMeasurement<S> for Time {
fn eval(&self, state: &S) -> String {
format!("{} (step {})", SiScale::format_short(state.time(), "s"), state.step_no())
}
fn key_value(&self, state: &S) -> Vec<Measurement>
vec![
Measurement::new_unitless("step", state.step_no()),
Measurement::new("time", state.time(), "s"),
]
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Meta;
impl<S: AbstractSim> AbstractMeasurement<S> for Meta {
fn eval(&self, state: &S) -> String {
format!("{}x{}x{} feat: {:.1e}m", state.width(), state.height(), state.depth(), state.feature_size())
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
vec![
Measurement::new_unitless("dim", state.dim()),
Measurement::new("feature_size", state.feature_size(), "m"),
]
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Volume {
name: String,
region: Box<dyn Region>,
}
impl Volume {
pub fn new<R: Region + 'static>(name: &str, r: R) -> Self {
Self {
name: name.into(),
region: Box::new(r)
}
}
/// Returns the volume of the region, in units of um^3
fn data<S: AbstractSim>(&self, state: &S) -> f32 {
let feat_um = state.feature_size() as f64 * 1e6;
(state.volume_of_region(&*self.region) as f64 * feat_um * feat_um * feat_um) as f32
}
}
impl<S: AbstractSim> AbstractMeasurement<S> for Volume {
fn eval(&self, state: &S) -> String {
format!("Vol({}): {:.2e} um^3",
self.name,
self.data(state),
)
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
vec![
Measurement::new(&format!("Vol({})", self.name), self.data(), "um^3"),
]
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Current {
name: String,
region: Box<dyn Region>,
}
impl Current {
pub fn new<R: Region + 'static>(name: &str, r: R) -> Self {
Self {
name: name.into(),
region: Box::new(r)
}
}
fn data<S: AbstractSim>(&self, state: &S) -> (f32, Vec3<f32>) {
let FieldSample(volume, current_mag, current_vec) = state.map_sum_over_enumerated(&*self.region, |coord: Meters, _cell| {
let current = state.current(coord);
FieldSample(1, current.mag().cast(), current.cast())
});
let mean_current_mag = current_mag.to_f32() / (f32::from_primitive(volume));
let mean_current_vec = current_vec.cast::<f32>() / (f32::from_primitive(volume));
(mean_current_mag, mean_current_vec.cast())
}
}
#[derive(Default)]
struct FieldSample(u32, f64, Vec3<f64>);
impl std::iter::Sum for FieldSample {
fn sum<I>(iter: I) -> Self
where I: Iterator<Item = Self>
{
let mut s = FieldSample::default();
for FieldSample(a, b, c) in iter {
s = FieldSample(s.0 + a, s.1 + b, s.2 + c);
}
s
}
}
#[derive(Default)]
struct FieldSamples<T>(T);
impl std::iter::Sum for FieldSamples<[FieldSample; 2]> {
fn sum<I>(iter: I) -> Self
where I: Iterator<Item = Self>
{
let mut s = Self::default();
for p in iter {
s.0[0] = FieldSample(s.0[0].0 + p.0[0].0, s.0[0].1 + p.0[0].1, s.0[0].2 + p.0[0].2);
s.0[1] = FieldSample(s.0[1].0 + p.0[1].0, s.0[1].1 + p.0[1].1, s.0[1].2 + p.0[1].2);
}
s
}
}
impl std::iter::Sum for FieldSamples<[FieldSample; 3]> {
fn sum<I>(iter: I) -> Self
where I: Iterator<Item = Self>
{
let mut s = Self::default();
for p in iter {
s.0[0] = FieldSample(s.0[0].0 + p.0[0].0, s.0[0].1 + p.0[0].1, s.0[0].2 + p.0[0].2);
s.0[1] = FieldSample(s.0[1].0 + p.0[1].0, s.0[1].1 + p.0[1].1, s.0[1].2 + p.0[1].2);
s.0[2] = FieldSample(s.0[2].0 + p.0[2].0, s.0[2].1 + p.0[2].1, s.0[2].2 + p.0[2].2);
}
s
}
}
impl<S: AbstractSim> AbstractMeasurement<S> for Current {
fn eval(&self, state: &S) -> String {
let (mean_current_mag, mean_current_vec) = self.data(state);
format!("I/cell({}): {:.2e} {:.2e}",
self.name,
mean_current_mag,
mean_current_vec)
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
let (mean_current_mag, mean_current_vec) = self.data(state);
vec![
Measurement::new(
&format!("Imag/cell({})", self.name),
mean_current_mag,
"A",
),
Measurement::new(
&format!("/cell({})", self.name),
mean_current_vec,
"A",
),
]
}
}
/// Measures the current directed around a closed loop
#[derive(Clone, Serialize, Deserialize)]
pub struct CurrentLoop {
name: String,
region: Torus
}
impl CurrentLoop {
pub fn new(name: &str, r: Torus) -> Self {
Self {
name: name.into(),
region: r,
}
}
fn data<S: AbstractSim>(&self, state: &S) -> f32 {
let FieldSample(volume, directed_current, _current_vec) = state.map_sum_over_enumerated(&self.region, |coord: Meters, _cell| {
let normal = self.region.axis();
let to_coord = *coord - *self.region.center();
let tangent = normal.cross(to_coord).norm();
let current = state.current(coord);
let directed_current = current.dot(tangent.cast());
FieldSample(1, directed_current.cast(), current.cast())
});
let mean_directed_current = directed_current.cast::<f32>() / f32::from_primitive(volume);
let cross_section = self.region.cross_section() / (state.feature_size() * state.feature_size());
let cross_sectional_current = mean_directed_current * cross_section;
cross_sectional_current
}
}
impl<S: AbstractSim> AbstractMeasurement<S> for CurrentLoop {
fn eval(&self, state: &S) -> String {
let cross_sectional_current = self.data(state);
format!("I({}): {:.2e}", self.name, cross_sectional_current)
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
let cross_sectional_current = self.data(state);
vec![
Measurement::new(
&format!("I({})", self.name),
cross_sectional_current,
"A"
),
]
}
}
/// Measures the M, B field directed around a closed loop
#[derive(Clone, Serialize, Deserialize)]
pub struct MagneticLoop {
name: String,
region: Torus
}
impl MagneticLoop {
pub fn new(name: &str, r: Torus) -> Self {
Self {
name: name.into(),
region: r,
}
}
fn data<S: AbstractSim>(&self, state: &S) -> (f32, f32, f32) {
let FieldSamples([
FieldSample(volume, directed_m, _m_vec),
FieldSample(_, directed_b, _b_vec),
FieldSample(_, directed_h, _h_vec),
]) = state.map_sum_over_enumerated(&self.region, |coord: Meters, cell|
{
let normal = self.region.axis();
let to_coord = *coord - *self.region.center();
let tangent = normal.cross(to_coord).norm();
let m = cell.m();
let directed_m = m.dot(tangent.cast());
let b = cell.b();
let directed_b = b.dot(tangent.cast());
let h = cell.h();
let directed_h = h.dot(tangent.cast());
FieldSamples([
FieldSample(1, directed_m.cast(), m.cast()),
FieldSample(1, directed_b.cast(), b.cast()),
FieldSample(1, directed_h.cast(), h.cast()),
])
});
// let cross_section = self.region.cross_section() / (state.feature_size() * state.feature_size());
let mean_directed_m = directed_m.cast::<f32>() / f32::from_primitive(volume);
// let cross_sectional_m = mean_directed_m * cross_section;
let mean_directed_b = directed_b.cast::<f32>() / f32::from_primitive(volume);
// let cross_sectional_b = mean_directed_b * cross_section;
let mean_directed_h = directed_h.cast::<f32>() / f32::from_primitive(volume);
// let cross_sectional_h = mean_directed_h * cross_section;
// format!(
// "M({}): {:.2e}; B({}): {:.2e}; H({}): {:.2e}",
// self.name, cross_sectional_m,
// self.name, cross_sectional_b,
// self.name, cross_sectional_h,
// )
(mean_directed_m, mean_directed_b, mean_directed_h)
}
}
impl<S: AbstractSim> AbstractMeasurement<S> for MagneticLoop {
fn eval(&self, state: &S) -> String {
let (mean_directed_m, mean_directed_b, mean_directed_h) = self.data(state);
format!(
"M({}): {:.2e}; B({}): {:.2e}; H({}): {:.2e}",
self.name, mean_directed_m,
self.name, mean_directed_b,
self.name, mean_directed_h,
)
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
let (mean_directed_m, mean_directed_b, mean_directed_h) = self.data(state);
vec![
Measurement::new_unitless(&format!("M({})", self.name), mean_directed_current_m),
Beasurement::new_unitless(&format!("B({})", self.name), mean_directed_current_b),
Beasurement::new_unitless(&format!("H({})", self.name), mean_directed_current_h),
]
}
}
/// mean M over a region
#[derive(Clone, Serialize, Deserialize)]
pub struct MagneticFlux {
name: String,
region: Box<dyn Region>,
}
impl MagneticFlux {
pub fn new<R: Region + 'static>(name: &str, r: R) -> Self {
Self {
name: name.into(),
region: Box::new(r)
}
}
fn data<S: AbstractSim>(&self, state: &S) -> Vec3<f32> {
let FieldSample(volume, _directed_mag, mag_vec) = state.map_sum_over(&*self.region, |cell| {
let b = cell.b();
let mag = b.mag();
FieldSample(1, mag.cast(), b.cast())
});
let mean_mag = mag_vec.cast() / f32::from_primitive(volume);
mean_mag
}
}
impl<S: AbstractSim> AbstractMeasurement<S> for MagneticFlux {
fn eval(&self, state: &S) -> String {
let mean_mag = self.data(state);
format!("Bavg({}): {:.2e}", self.name, mean_mag)
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
let mean_mag = self.data(state);
vec![
Measurement::new_unitless(
&format!("Bavg({})", self.name),
mean_mag,
)
]
}
}
/// mean B over a region
#[derive(Clone, Serialize, Deserialize)]
pub struct Magnetization {
name: String,
region: Box<dyn Region>,
}
impl Magnetization {
pub fn new<R: Region + 'static>(name: &str, r: R) -> Self {
Self {
name: name.into(),
region: Box::new(r)
}
}
fn data<S: AbstractSim>(&self, state: &S) -> Vec3<f32> {
let FieldSample(volume, _directed_mag, mag_vec) = state.map_sum_over(&*self.region, |cell| {
let m = cell.m();
let mag = m.mag();
FieldSample(1, mag.cast(), m.cast())
});
let mean_mag = mag_vec.cast() / f32::from_primitive(volume);
mean_mag
}
}
impl<S: AbstractSim> AbstractMeasurement<S> for Magnetization {
fn eval(&self, state: &S) -> String {
let mean_mag = self.data(state);
format!("Mavg({}): {:.2e}", self.name, mean_mag)
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
let mean_mag = self.data(state);
vec![
Measurement::new_unitless(
&format!("Mavg({})", self.name), mean_mag
),
]
}
}
fn loc(v: Meters) -> String {
format!("{:.0} um", *v * f32::from_primitive(1_000_000))
}
/// M
#[derive(Clone, Serialize, Deserialize)]
pub struct MagnetizationAt(pub Meters);
impl<S: AbstractSim> AbstractMeasurement<S> for MagnetizationAt {
fn eval(&self, state: &S) -> String {
let m = state.sample(self.0).m();
format!("M{}: {:.2e}", loc(self.0), m)
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
let m = state.sample(self.0).m();
vec![
Measurement::new_unitless(&format!("M{}", loc(self.0)), m)
]
}
}
/// B
#[derive(Clone, Serialize, Deserialize)]
pub struct MagneticFluxAt(pub Meters);
impl<S: AbstractSim> AbstractMeasurement<S> for MagneticFluxAt {
fn eval(&self, state: &S) -> String {
let b = state.sample(self.0).b();
format!("B{}: {:.2e}", loc(self.0), b)
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
let b = state.sample(self.0).b();
vec![
Measurement::new_unitless(
&format!("B{}", loc(self.0)), b
)
]
}
}
/// H
#[derive(Clone, Serialize, Deserialize)]
pub struct MagneticStrengthAt(pub Meters);
impl<S: AbstractSim> AbstractMeasurement<S> for MagneticStrengthAt {
fn eval(&self, state: &S) -> String {
let h = state.sample(self.0).h();
format!("H{}: {:.2e}", loc(self.0), h)
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
let h = state.sample(self.0).h();
vec![
Measurement::new_unitless(
&format!("H{}", loc(self.0)), h
)
]
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct ElectricField(pub Meters);
impl<S: AbstractSim> AbstractMeasurement<S> for ElectricField {
fn eval(&self, state: &S) -> String {
let e = state.sample(self.0).e();
format!("E{}: {}", loc(self.0), e)
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
let e = state.sample(self.0).e();
vec![
Measurement::new_unitless(
&format!("E{}", loc(self.0)), e
)
]
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Energy {
name: String,
region: Box<dyn Region>,
}
impl Energy {
pub fn world() -> Self {
Self::new("World", WorldRegion)
}
pub fn new<R: Region + 'static>(name: &str, region: R) -> Self {
Self {
name: name.into(),
region: Box::new(region),
}
}
fn data<S: AbstractSim>(&self, state: &S) -> f32 {
// Potential energy stored in a E/M field:
// https://en.wikipedia.org/wiki/Magnetic_energy
// https://en.wikipedia.org/wiki/Electric_potential_energy#Energy_stored_in_an_electrostatic_field_distribution
// TODO: consider the M field? https://en.wikipedia.org/wiki/Potential_energy#Magnetic_potential_energy
// U(B) = 1/2 \int H . B dV
// U(E) = 1/2 \int E . D dV
#[allow(non_snake_case)]
let dV = state.feature_volume();
let e = f64::from_primitive(0.5 * dV) * state.map_sum_over(&*self.region, |cell| {
// E . D = E . (E + P) = E.E since we don't model polarization fields
cell.h().dot(cell.b()).to_f64() + cell.e().mag_sq().to_f64()
});
e.cast()
}
}
impl<S: AbstractSim> AbstractMeasurement<S> for Energy {
fn eval(&self, state: &S) -> String {
let e = self.data(state);
format!("U({}): {}", self.name, SiScale::format_short(e, "J"))
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
let e = self.data(state);
vec![
Measurement::new_unitless(
&format!("U({})", self.name), e
)
]
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Power {
name: String,
region: Box<dyn Region>
}
impl Power {
pub fn world() -> Self {
Self::new("World", WorldRegion)
}
pub fn new<R: Region + 'static>(name: &str, region: R) -> Self {
Self {
name: name.into(),
region: Box::new(region),
}
}
fn data<S: AbstractSim>(&self, state: &S) -> f32 {
// Power is P = IV = A*J*V = L^2*J.(LE) = L^3 J.E
// where L is feature size.
#[allow(non_snake_case)]
let dV = state.feature_volume();
let power = f64::from_primitive(dV) * state.map_sum_over(&*self.region, |cell| {
cell.current_density().dot(cell.e()).to_f64()
});
power.cast()
}
}
impl<S: AbstractSim> AbstractMeasurement<S> for Power {
fn eval(&self, state: &S) -> String {
let power = self.data(state);
format!("P({}): {}", self.name, SiScale::format_short(power, "W"))
}
fn key_value(&self, state: &S) -> Vec<Measurement> {
let power = self.data(state);
vec![
Measurement::new(
&format!("P({})", self.name), power, "W"
)
]
}
}