use crate::geom::{Meters, Region, Torus, Vec3, WorldRegion}; use crate::real::{Real as _, ToFloat as _}; use crate::sim::SampleableSim; use dyn_clone::{self, DynClone}; use indexmap::IndexMap; use serde::{Serialize, Deserialize}; // TODO: remove this Clone and Send requirement? Have Measurements be shared by-reference across // threads? i.e. Sync, and no Clone #[typetag::serde(tag = "type")] pub trait AbstractMeasurement: Send + Sync + DynClone { fn eval(&self, state: &dyn SampleableSim) -> String; fn key_value(&self, state: &dyn SampleableSim) -> IndexMap; } dyn_clone::clone_trait_object!(AbstractMeasurement); pub fn eval_multiple_kv(state: &dyn SampleableSim, meas: &[Box]) -> IndexMap { let mut r = IndexMap::new(); for m in meas { let other = m.key_value(state); r.extend(other.into_iter()); } r } #[derive(Clone, Serialize, Deserialize)] pub struct Time; #[typetag::serde] impl AbstractMeasurement for Time { fn eval(&self, state: &dyn SampleableSim) -> String { format!("{:.3e}s (step {})", state.time(), state.step_no()) } fn key_value(&self, state: &dyn SampleableSim) -> IndexMap { [ ("step".to_string(), state.step_no().to_string()), ("time".to_string(), state.time().to_string()), ].into_iter().collect() } } #[derive(Clone, Serialize, Deserialize)] pub struct Meta; #[typetag::serde] impl AbstractMeasurement for Meta { fn eval(&self, state: &dyn SampleableSim) -> String { format!("{}x{}x{} feat: {:.1e}m", state.width(), state.height(), state.depth(), state.feature_size()) } fn key_value(&self, state: &dyn SampleableSim) -> IndexMap { [ ("width".to_string(), state.width().to_string()), ("height".to_string(), state.height().to_string()), ("depth".to_string(), state.depth().to_string()), ("feature_size".to_string(), state.feature_size().to_string()), ].into_iter().collect() } } #[derive(Clone, Serialize, Deserialize)] pub struct Label(pub String); impl Label { pub fn new>(s: S) -> Self { Self(s.into()) } } #[typetag::serde] impl AbstractMeasurement for Label { fn eval(&self, _state: &dyn SampleableSim) -> String { self.0.clone() } fn key_value(&self, _state: &dyn SampleableSim) -> IndexMap { [ (self.0.clone(), self.0.clone()), ].into_iter().collect() } } #[derive(Clone, Serialize, Deserialize)] pub struct Volume { name: String, region: Box, } impl Volume { pub fn new(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(&self, state: &dyn SampleableSim) -> 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 } } #[typetag::serde] impl AbstractMeasurement for Volume { fn eval(&self, state: &dyn SampleableSim) -> String { format!("Vol({}): {:.2e} um^3", self.name, self.data(state), ) } fn key_value(&self, state: &dyn SampleableSim) -> IndexMap { [ (format!("Vol({})", self.name), self.data(state).to_string()), ].into_iter().collect() } } #[derive(Clone, Serialize, Deserialize)] pub struct Current { name: String, region: Box, } impl Current { pub fn new(name: &str, r: R) -> Self { Self { name: name.into(), region: Box::new(r) } } fn data(&self, state: &dyn SampleableSim) -> (f32, Vec3) { 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::from_primitive(volume)); (mean_current_mag, mean_current_vec.cast()) } } #[derive(Default)] struct FieldSample(u32, f64, Vec3); impl std::iter::Sum for FieldSample { fn sum(iter: I) -> Self where I: Iterator { 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); impl std::iter::Sum for FieldSamples<[FieldSample; 2]> { fn sum(iter: I) -> Self where I: Iterator { 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(iter: I) -> Self where I: Iterator { 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 } } #[typetag::serde] impl AbstractMeasurement for Current { fn eval(&self, state: &dyn SampleableSim) -> 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: &dyn SampleableSim) -> IndexMap { let (mean_current_mag, mean_current_vec) = self.data(state); [ (format!("Imag/cell({})", self.name), mean_current_mag.to_string()), (format!("I/cell({})", self.name), mean_current_vec.to_string()), ].into_iter().collect() } } /// 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(&self, state: &dyn SampleableSim) -> 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::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 } } #[typetag::serde] impl AbstractMeasurement for CurrentLoop { fn eval(&self, state: &dyn SampleableSim) -> String { let cross_sectional_current = self.data(state); format!("I({}): {:.2e}", self.name, cross_sectional_current) } fn key_value(&self, state: &dyn SampleableSim) -> IndexMap { let cross_sectional_current = self.data(state); [ (format!("I({})", self.name), cross_sectional_current.to_string()), ].into_iter().collect() } } /// 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(&self, state: &dyn SampleableSim) -> (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::from_primitive(volume); // let cross_sectional_m = mean_directed_m * cross_section; let mean_directed_b = directed_b.cast::() / f32::from_primitive(volume); // let cross_sectional_b = mean_directed_b * cross_section; let mean_directed_h = directed_h.cast::() / 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) } } #[typetag::serde] impl AbstractMeasurement for MagneticLoop { fn eval(&self, state: &dyn SampleableSim) -> 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: &dyn SampleableSim) -> IndexMap { let (mean_directed_m, mean_directed_b, mean_directed_h) = self.data(state); [ (format!("M({})", self.name), mean_directed_m.to_string()), (format!("B({})", self.name), mean_directed_b.to_string()), (format!("H({})", self.name), mean_directed_h.to_string()), ].into_iter().collect() } } /// mean M over a region #[derive(Clone, Serialize, Deserialize)] pub struct MagneticFlux { name: String, region: Box, } impl MagneticFlux { pub fn new(name: &str, r: R) -> Self { Self { name: name.into(), region: Box::new(r) } } fn data(&self, state: &dyn SampleableSim) -> Vec3 { 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 } } #[typetag::serde] impl AbstractMeasurement for MagneticFlux { fn eval(&self, state: &dyn SampleableSim) -> String { let mean_mag = self.data(state); format!("Bavg({}): {:.2e}", self.name, mean_mag) } fn key_value(&self, state: &dyn SampleableSim) -> IndexMap { let mean_mag = self.data(state); [ (format!("Bavg({})", self.name), mean_mag.to_string()), ].into_iter().collect() } } /// mean B over a region #[derive(Clone, Serialize, Deserialize)] pub struct Magnetization { name: String, region: Box, } impl Magnetization { pub fn new(name: &str, r: R) -> Self { Self { name: name.into(), region: Box::new(r) } } fn data(&self, state: &dyn SampleableSim) -> Vec3 { 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 } } #[typetag::serde] impl AbstractMeasurement for Magnetization { fn eval(&self, state: &dyn SampleableSim) -> String { let mean_mag = self.data(state); format!("Mavg({}): {:.2e}", self.name, mean_mag) } fn key_value(&self, state: &dyn SampleableSim) -> IndexMap { let mean_mag = self.data(state); [ (format!("Mavg({})", self.name), mean_mag.to_string()), ].into_iter().collect() } } 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); #[typetag::serde] impl AbstractMeasurement for MagnetizationAt { fn eval(&self, state: &dyn SampleableSim) -> String { let m = state.sample(self.0).m(); format!("M{}: {:.2e}", loc(self.0), m) } fn key_value(&self, state: &dyn SampleableSim) -> IndexMap { let m = state.sample(self.0).m(); [ (format!("M{}", loc(self.0)), m.to_string()), ].into_iter().collect() } } /// B #[derive(Clone, Serialize, Deserialize)] pub struct MagneticFluxAt(pub Meters); #[typetag::serde] impl AbstractMeasurement for MagneticFluxAt { fn eval(&self, state: &dyn SampleableSim) -> String { let b = state.sample(self.0).b(); format!("B{}: {:.2e}", loc(self.0), b) } fn key_value(&self, state: &dyn SampleableSim) -> IndexMap { let b = state.sample(self.0).b(); [ (format!("B{}", loc(self.0)), b.to_string()), ].into_iter().collect() } } /// H #[derive(Clone, Serialize, Deserialize)] pub struct MagneticStrengthAt(pub Meters); #[typetag::serde] impl AbstractMeasurement for MagneticStrengthAt { fn eval(&self, state: &dyn SampleableSim) -> String { let h = state.sample(self.0).h(); format!("H{}: {:.2e}", loc(self.0), h) } fn key_value(&self, state: &dyn SampleableSim) -> IndexMap { let h = state.sample(self.0).h(); [ (format!("H{}", loc(self.0)), h.to_string()), ].into_iter().collect() } } #[derive(Clone, Serialize, Deserialize)] pub struct ElectricField(pub Meters); #[typetag::serde] impl AbstractMeasurement for ElectricField { fn eval(&self, state: &dyn SampleableSim) -> String { let e = state.sample(self.0).e(); format!("E{}: {:.2e}", loc(self.0), e) } fn key_value(&self, state: &dyn SampleableSim) -> IndexMap { let e = state.sample(self.0).e(); [ (format!("E{}", loc(self.0)), e.to_string()), ].into_iter().collect() } } #[derive(Clone, Serialize, Deserialize)] pub struct Energy { name: String, region: Box, } impl Energy { pub fn world() -> Self { Self::new("World", WorldRegion) } pub fn new(name: &str, region: R) -> Self { Self { name: name.into(), region: Box::new(region), } } fn data(&self, state: &dyn SampleableSim) -> 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() } } #[typetag::serde] impl AbstractMeasurement for Energy { fn eval(&self, state: &dyn SampleableSim) -> String { let e = self.data(state); format!("U({}): {:.2e}", self.name, e) } fn key_value(&self, state: &dyn SampleableSim) -> IndexMap { let e = self.data(state); [ (format!("U({})", self.name), e.to_string()), ].into_iter().collect() } } #[derive(Clone, Serialize, Deserialize)] pub struct Power { name: String, region: Box } impl Power { pub fn world() -> Self { Self::new("World", WorldRegion) } pub fn new(name: &str, region: R) -> Self { Self { name: name.into(), region: Box::new(region), } } fn data(&self, state: &dyn SampleableSim) -> 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() } } #[typetag::serde] impl AbstractMeasurement for Power { fn eval(&self, state: &dyn SampleableSim) -> String { let power = self.data(state); format!("P({}): {:.2e}", self.name, power) } fn key_value(&self, state: &dyn SampleableSim) -> IndexMap { let power = self.data(state); [ (format!("P({})", self.name), power.to_string()), ].into_iter().collect() } }