stim: break apart into smaller modules

This commit is contained in:
colin 2022-08-24 15:27:40 -07:00
parent 9301734fcf
commit 7f089bad45
5 changed files with 809 additions and 756 deletions

View File

@ -1,756 +0,0 @@
use crate::real::{self, Real};
use crate::cross::vec::Vec3;
use crate::geom::{Coord as _, HasCrossSection, Index, Meters, Region};
use coremem_cross::dim::DimSlice;
use coremem_cross::vec::Vec3u;
use std::borrow::Cow;
use std::ops::Deref;
use rand;
/// field densities
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Fields<R> {
pub e: Vec3<R>,
pub h: Vec3<R>,
}
impl<R> Fields<R> {
pub fn new_eh(e: Vec3<R>, h: Vec3<R>) -> Self {
Self { e, h}
}
}
impl<R: Real> Fields<R> {
pub fn e(&self) -> Vec3<R> {
self.e
}
pub fn h(&self) -> Vec3<R> {
self.h
}
pub fn new_e(e: Vec3<R>) -> Self {
Self::new_eh(e, Vec3::zero())
}
pub fn new_h(h: Vec3<R>) -> Self {
Self::new_eh(Vec3::zero(), h)
}
pub fn elem_mul(self, other: FieldMags<R>) -> Fields<R> {
Fields {
e: self.e * other.e,
h: self.h * other.h,
}
}
}
impl<R: Real> std::ops::AddAssign for Fields<R> {
fn add_assign(&mut self, other: Self) {
self.e += other.e;
self.h += other.h;
}
}
impl<R: Real> std::ops::Add for Fields<R> {
type Output = Self;
fn add(mut self, other: Self) -> Self::Output {
self += other;
self
}
}
impl<R: Real> std::ops::Mul<R> for Fields<R> {
type Output = Self;
fn mul(self, scale: R) -> Self::Output {
Fields {
e: self.e * scale,
h: self.h * scale,
}
}
}
/// field magnitude densities (really, signed magnitude)
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct FieldMags<R> {
pub e: R,
pub h: R,
}
impl<R: Real> std::ops::AddAssign for FieldMags<R> {
fn add_assign(&mut self, other: Self) {
self.e += other.e;
self.h += other.h;
}
}
impl<R: Real> std::ops::Add for FieldMags<R> {
type Output = Self;
fn add(mut self, other: Self) -> Self::Output {
self += other;
self
}
}
impl<R: Real> std::ops::Mul<R> for FieldMags<R> {
type Output = Self;
fn mul(self, scale: R) -> Self::Output {
FieldMags {
e: self.e * scale,
h: self.h * scale,
}
}
}
impl<R> FieldMags<R> {
pub fn new_eh(e: R, h: R) -> Self {
Self { e, h }
}
}
impl<R: Real> FieldMags<R> {
pub fn e(&self) -> R {
self.e
}
pub fn h(&self) -> R {
self.h
}
pub fn new_e(e: R) -> Self {
Self::new_eh(e, R::zero())
}
pub fn new_h(h: R) -> Self {
Self::new_eh(R::zero(), h)
}
pub fn elem_mul(self, other: Self) -> Self {
FieldMags {
e: self.e * other.e,
h: self.h * other.h,
}
}
}
pub trait Stimulus<R: Real>: Sync {
/// Return the (E, H) field which should be added PER-SECOND to the provided position/time.
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R>;
/// compute the value of this stimulus across all the simulation space
fn rendered<'a>(
&'a self, scale: R, t_sec: R, feature_size: R, dim: Vec3u
) -> Cow<'a, RenderedStimulus<R>> {
Cow::Owned(render_stim(self, scale, t_sec, feature_size, dim))
}
}
fn render_stim<R: Real, S: Stimulus<R> + ?Sized>(
stim: &S, scale: R, t_sec: R, feature_size: R, dim: Vec3u
) -> RenderedStimulus<R> {
let dim_len = dim.product_sum_usize();
let mut e = Vec::new();
e.resize_with(dim_len, Default::default);
let mut h = Vec::new();
h.resize_with(dim_len, Default::default);
rayon::scope(|s| {
let mut undispatched_e = &mut e[..];
let mut undispatched_h = &mut h[..];
for z in 0..dim.z() {
for y in 0..dim.y() {
let (this_e, this_h);
(this_e, undispatched_e) = undispatched_e.split_at_mut(dim.x() as usize);
(this_h, undispatched_h) = undispatched_h.split_at_mut(dim.x() as usize);
s.spawn(move |_| {
for (x, (out_e, out_h)) in this_e.iter_mut().zip(this_h.iter_mut()).enumerate() {
let Fields { e, h } = stim.at(t_sec, feature_size, Index::new(x as u32, y, z));
*out_e = e * scale;
*out_h = h * scale;
}
});
}
}
});
let field_e = DimSlice::new(dim, e);
let field_h = DimSlice::new(dim, h);
RenderedStimulus::new(
field_e, field_h, scale, feature_size, t_sec
)
}
/// a static vector field. different value at each location, but constant in time.
/// often used as a building block by wrapping it in something which modulates the fields over
/// time.
pub trait VectorField<R> {
fn at(&self, feat_size: R, loc: Index) -> Fields<R>;
}
// uniform vector field
impl<R: Real> VectorField<R> for Fields<R> {
fn at(&self, _feat_size: R, _loc: Index) -> Fields<R> {
*self
}
}
// could broaden this and implement directly on T, but blanket impls
// are unwieldy
impl<R: Real, T> VectorField<R> for DimSlice<T>
where
DimSlice<T>: core::ops::Index<Vec3u, Output=Fields<R>>
{
fn at(&self, _feat_size: R, loc: Index) -> Fields<R> {
self[loc.into()]
}
}
#[derive(Clone)]
pub struct RenderedStimulus<R> {
e: DimSlice<Vec<Vec3<R>>>,
h: DimSlice<Vec<Vec3<R>>>,
scale: R,
feature_size: R,
t_sec: R,
}
impl<R> RenderedStimulus<R> {
pub fn new(
e: DimSlice<Vec<Vec3<R>>>,
h: DimSlice<Vec<Vec3<R>>>,
scale: R,
feature_size: R,
t_sec: R,
) -> Self {
Self { e, h, scale, feature_size, t_sec }
}
pub fn e<'a>(&'a self) -> DimSlice<&'a [Vec3<R>]> {
self.e.as_ref()
}
pub fn h<'a>(&'a self) -> DimSlice<&'a [Vec3<R>]> {
self.h.as_ref()
}
}
impl<R: Real> RenderedStimulus<R> {
pub fn scale(&self) -> R {
self.scale
}
pub fn feature_size(&self) -> R {
self.feature_size
}
pub fn time(&self) -> R {
self.t_sec
}
}
impl<R: Real> VectorField<R> for RenderedStimulus<R> {
fn at(&self, _feat_size: R, loc: Index) -> Fields<R> {
Fields::new_eh(self.e[loc.into()], self.h[loc.into()])
}
}
impl<R: Real> Stimulus<R> for RenderedStimulus<R> {
fn at(&self, _t_sec: R, _feat_size: R, loc: Index) -> Fields<R> {
Fields::new_eh(self.e[loc.into()], self.h[loc.into()])
}
fn rendered<'a>(
&'a self, scale: R, t_sec: R, feature_size: R, dim: Vec3u
) -> Cow<'a, RenderedStimulus<R>> {
if (self.scale, self.t_sec, self.feature_size, self.e.dim()) == (scale, t_sec, feature_size, dim) {
Cow::Borrowed(self)
} else {
Cow::Owned(render_stim(self, scale, t_sec, feature_size, dim))
}
}
}
impl<R: Real> Stimulus<R> for Fields<R> {
fn at(&self, _t_sec: R, _feat_size: R, _loc: Index) -> Fields<R> {
*self
}
}
pub struct ModulatedVectorField<V, T> {
fields: V,
modulation: T,
}
impl<V, T> ModulatedVectorField<V, T> {
pub fn new(fields: V, modulation: T) -> Self {
Self { fields, modulation }
}
pub fn into_inner(self) -> (V, T) {
(self.fields, self.modulation)
}
pub fn fields(&self) -> &V {
&self.fields
}
pub fn modulation(&self) -> &T {
&self.modulation
}
}
impl<R: Real, V: VectorField<R> + Sync, T: TimeVarying<R> + Sync> Stimulus<R> for ModulatedVectorField<V, T> {
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R> {
self.fields.at(feat_size, loc).elem_mul(self.modulation.at(t_sec))
}
}
/// used as a MapVisitor in order to evaluate each Stimulus in a List at a specific time/place.
// struct StimulusEvaluator {
// fields: Fields,
// t_sec: f32,
// feat_size: f32,
// loc: Index,
// }
//
// impl<S: Stimulus> Visitor<&S> for &mut StimulusEvaluator {
// fn visit(&mut self, next: &S) {
// self.fields += next.at(self.t_sec, self.feat_size, self.loc);
// }
// }
//
// impl<L: Sync> Stimulus for L
// where
// for<'a, 'b> &'a L: Visit<&'b mut StimulusEvaluator>,
// {
// fn at(&self, t_sec: f32, pos: Meters) -> Fields {
// let mut ev = StimulusEvaluator { t_sec, pos, fields: Fields::default()};
// self.visit(&mut ev);
// ev.fields
// }
// }
// conflicts with List implementation
// impl<T: Stimulus> Stimulus for &T {
// fn at(&self, t_sec: f32, feat_size: f32, loc: Index) -> Fields {
// (*self).at(t_sec, feat_size, loc)
// }
// }
pub struct StimuliVec<S>(Vec<S>);
pub type DynStimuli<R> = StimuliVec<Box<dyn Stimulus<R> + Send>>;
impl<S> Default for StimuliVec<S> {
fn default() -> Self {
Self(Vec::new())
}
}
impl<S> Deref for StimuliVec<S> {
type Target = Vec<S>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<S> StimuliVec<S> {
pub fn new() -> Self {
Self::default()
}
pub fn from_vec(stim: Vec<S>) -> Self {
Self(stim)
}
pub fn push(&mut self, a: S) {
self.0.push(a)
}
}
impl<R: Real, S: Stimulus<R>> Stimulus<R> for StimuliVec<S> {
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R> {
self.0.iter().map(|i| i.at(t_sec, feat_size, loc))
.fold(Fields::default(), core::ops::Add::add)
}
}
impl<R: Real> Stimulus<R> for Box<dyn Stimulus<R> + Send> {
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R> {
(**self).at(t_sec, feat_size, loc)
}
}
pub struct NoopStimulus;
impl<R: Real> Stimulus<R> for NoopStimulus {
fn at(&self, _t_sec: R, _feat_size: R, _loc: Index) -> Fields<R> {
Fields::default()
}
}
pub struct RngStimulus {
seed: u64,
e_scale: f32,
h_scale: f32,
}
impl RngStimulus {
pub fn new(seed: u64) -> Self {
Self { seed, e_scale: 1e15, h_scale: 1e15 }
}
pub fn new_e(seed: u64) -> Self {
Self { seed, e_scale: 1e15, h_scale: 0.0 }
}
fn gen(&self, t_sec: f32, pos: Meters, scale: f32, salt: u64) -> Vec3<f32> {
use rand::{Rng as _, SeedableRng as _};
let seed = self.seed
^ (t_sec.to_bits() as u64)
^ ((pos.x().to_bits() as u64) << 8)
^ ((pos.y().to_bits() as u64) << 16)
^ ((pos.z().to_bits() as u64) << 24)
^ (salt << 32);
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
Vec3::new(
rng.gen_range(-scale..=scale),
rng.gen_range(-scale..=scale),
rng.gen_range(-scale..=scale),
)
}
}
impl<R: Real> Stimulus<R> for RngStimulus {
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R> {
Fields {
e: self.gen(t_sec.cast(), loc.to_meters(feat_size.cast()), self.e_scale, 0).cast(),
h: self.gen(t_sec.cast(), loc.to_meters(feat_size.cast()), self.h_scale, 0x7de3).cast(),
}
}
}
/// Apply a time-varying stimulus uniformly across some region
#[derive(Clone)]
pub struct RegionGated<G, S> {
region: G,
stim: S,
}
impl<G, S> RegionGated<G, S> {
pub fn new(region: G, stim: S) -> Self {
Self {
region, stim
}
}
}
impl<R: Real, G: Region + Sync, S: Stimulus<R> + Sync> Stimulus<R> for RegionGated<G, S> {
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R> {
if self.region.contains(loc.to_meters(feat_size.cast())) {
self.stim.at(t_sec, feat_size, loc)
} else {
Fields::default()
}
}
}
impl<R: Real, G: Region + Sync, S: VectorField<R>> VectorField<R> for RegionGated<G, S> {
fn at(&self, feat_size: R, loc: Index) -> Fields<R> {
if self.region.contains(loc.to_meters(feat_size.cast())) {
self.stim.at(feat_size, loc)
} else {
Fields::default()
}
}
}
/// apply a stimulus across some region.
/// the stimulus seen at each point is based on its angle about the specified ray.
/// the stimulus has equal E and H vectors. if you want just one, filter it one with `Scaled`.
#[derive(Clone)]
pub struct CurlVectorField<G> {
region: G,
}
impl<G> CurlVectorField<G> {
pub fn new(region: G) -> Self {
Self { region }
}
}
impl<R: Real, G: Region + HasCrossSection> VectorField<R> for CurlVectorField<G> {
fn at(&self, feat_size: R, loc: Index) -> Fields<R> {
let pos = loc.to_meters(feat_size.cast());
if self.region.contains(pos) {
// TODO: do we *want* this to be normalized?
let rotational = self.region.cross_section_normal(pos).norm().cast();
Fields::new_eh(rotational, rotational)
} else {
Fields::default()
}
}
}
pub trait StimExt<R>: Sized {
fn shifted(self, new_start: R) -> Shifted<R, Self> {
Shifted::new(self, new_start)
}
fn gated(self, from: R, to: R) -> Gated<R, Self> {
Gated::new(Pulse::new(from, to), self)
}
fn scaled<T: TimeVarying<R>>(self, scale: T) -> Scaled<Self, T> {
Scaled::new(self, scale)
}
fn summed<T: TimeVarying<R>>(self, with: T) -> Summed<Self, T> {
Summed::new(self, with)
}
}
impl<R, T> StimExt<R> for T {}
pub trait TimeVarying<R> {
fn at(&self, t_sec: R) -> FieldMags<R>;
}
impl<R: Real> TimeVarying<R> for FieldMags<R> {
fn at(&self, _t_sec: R) -> FieldMags<R> {
*self
}
}
// assumed to represent the E field
impl<R: Real> TimeVarying<real::Finite<R>> for real::Finite<R> {
fn at(&self, _t_sec: real::Finite<R>) -> FieldMags<real::Finite<R>> {
FieldMags::new_e(*self)
}
}
impl TimeVarying<f32> for f32 {
fn at(&self, _t_sec: f32) -> FieldMags<f32> {
FieldMags::new_e(*self)
}
}
impl TimeVarying<f64> for f64 {
fn at(&self, _t_sec: f64) -> FieldMags<f64> {
FieldMags::new_e(*self)
}
}
// Vec<T> at any `t_sec` behaves as the sum of all its components at that time.
impl<R: Real, T: TimeVarying<R>> TimeVarying<R> for Vec<T> {
fn at(&self, t_sec: R) -> FieldMags<R> {
self.iter().fold(FieldMags::default(), |acc, i| acc + i.at(t_sec))
}
}
pub struct UnitEH;
impl<R: Real> TimeVarying<R> for UnitEH {
fn at(&self, _t_sec: R) -> FieldMags<R> {
FieldMags::new_eh(R::one(), R::one())
}
}
/// E field which changes magnitude sinusoidally as a function of t
#[derive(Clone)]
pub struct Sinusoid<R> {
amp: R,
omega: R,
}
impl<R: Real> Sinusoid<R> {
pub fn new(amp: R, freq: R) -> Self {
Self {
amp,
omega: freq * R::two_pi(),
}
}
pub fn from_wavelength(amp: R, lambda: R) -> Self {
Self::new(amp, lambda.inv())
}
pub fn freq(&self) -> R {
self.omega / R::two_pi()
}
pub fn wavelength(&self) -> R {
self.freq().inv()
}
pub fn one_cycle(self) -> Gated<R, Self> {
let wl = self.wavelength();
self.gated(R::zero(), wl)
}
pub fn half_cycle(self) -> Gated<R, Self> {
let wl = self.wavelength();
self.gated(R::zero(), R::half() * wl)
}
}
impl<R: Real> TimeVarying<R> for Sinusoid<R> {
fn at(&self, t_sec: R) -> FieldMags<R> {
FieldMags {
e: self.amp * (t_sec * self.omega).sin(),
h: R::zero(),
}
}
}
/// E field that decays exponentially over t.
#[derive(Clone)]
pub struct Exp<R> {
tau: R,
}
impl<R: Real + TimeVarying<R>> Exp<R> {
pub fn new(half_life: R) -> Self {
let tau = R::ln2()/half_life;
Self { tau }
}
pub fn new_at(amp: R, start: R, half_life: R) -> Shifted<R, Gated<R, Scaled<Self, R>>> {
Self::new(half_life).scaled(amp).gated(R::zero(), half_life*100f32.cast::<R>()).shifted(start)
}
}
impl<R: Real> TimeVarying<R> for Exp<R> {
fn at(&self, t_sec: R) -> FieldMags<R> {
let a = (t_sec * -self.tau).exp();
FieldMags::new_eh(a, a)
}
}
/// pulses E=1.0 and H=1.0 over the provided duration.
/// this is used as a building block to gate some VectorField over a specific time.
#[derive(Clone)]
pub struct Pulse<R> {
start: R,
end: R,
}
impl<R> Pulse<R> {
pub fn new(start: R, end: R) -> Self {
Self { start, end }
}
}
impl<R: Real> Pulse<R> {
fn contains(&self, t: R) -> bool {
t >= self.start && t < self.end
}
}
impl<R: Real> TimeVarying<R> for Pulse<R> {
fn at(&self, t: R) -> FieldMags<R> {
if self.contains(t) {
FieldMags::new_eh(R::one(), R::one())
} else {
FieldMags::new_eh(R::zero(), R::zero())
}
}
}
pub type Gated<R, T> = Scaled<Pulse<R>, T>;
#[derive(Clone)]
pub struct Shifted<R, T> {
start_at: R,
inner: T,
}
impl<R, T> Shifted<R, T> {
pub fn new(inner: T, start_at: R) -> Self {
Self { inner, start_at }
}
}
impl<R: Real, T: TimeVarying<R>> TimeVarying<R> for Shifted<R, T> {
fn at(&self, t_sec: R) -> FieldMags<R> {
self.inner.at(t_sec - self.start_at)
}
}
impl<R: Real, T: Stimulus<R>> Stimulus<R> for Shifted<R, T> {
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R> {
self.inner.at(t_sec - self.start_at, feat_size, loc)
}
}
#[derive(Clone)]
pub struct Scaled<A, B>(A, B);
impl<A, B> Scaled<A, B> {
pub fn new(a: A, b: B) -> Self {
Self(a, b)
}
}
impl<R: Real, A: TimeVarying<R>, B: TimeVarying<R>> TimeVarying<R> for Scaled<A, B> {
fn at(&self, t_sec: R) -> FieldMags<R> {
self.0.at(t_sec).elem_mul(self.1.at(t_sec))
}
}
#[derive(Clone)]
pub struct Summed<A, B>(A, B);
impl<A, B> Summed<A, B> {
pub fn new(a: A, b: B) -> Self {
Self(a, b)
}
}
impl<R: Real, A: TimeVarying<R>, B: TimeVarying<R>> TimeVarying<R> for Summed<A, B> {
fn at(&self, t_sec: R) -> FieldMags<R> {
self.0.at(t_sec) + self.1.at(t_sec)
}
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! assert_approx_eq {
($x:expr, $e:expr, $h:expr) => {
let x = $x;
let e = $e;
let h = $h;
let diff_e = (x.e - e).abs();
assert!(diff_e <= 0.001, "{:?} != {:?}", x, e);
let diff_h = (x.h - h).abs();
assert!(diff_h <= 0.001, "{:?} != {:?}", x, h);
}
}
#[test]
fn sinusoid() {
let s = Sinusoid::new(10.0, 1000.0);
assert_eq!(s.at(0.0), FieldMags::default());
assert_approx_eq!(s.at(0.00025), 10.0, 0.0);
assert_approx_eq!(s.at(0.00050), 0.0, 0.0);
assert_approx_eq!(s.at(0.00075), -10.0, 0.0);
}
#[test]
fn sinusoid3_from_wavelength() {
let s = Sinusoid::from_wavelength(10.0, 0.001);
assert_eq!(s.at(0.0), FieldMags::default());
assert_approx_eq!(s.at(0.00025), 10.0, 0.0);
assert_approx_eq!(s.at(0.00050), 0.0, 0.0);
assert_approx_eq!(s.at(0.00075), -10.0, 0.0);
}
struct MockRegion {
normal: Vec3<f32>,
}
impl HasCrossSection for MockRegion {
fn cross_section_normal(&self, _p: Meters) -> Vec3<f32> {
self.normal
}
}
impl Region for MockRegion {
fn contains(&self, _p: Meters) -> bool {
true
}
}
#[test]
fn curl_stimulus_trivial() {
let region = MockRegion {
normal: Vec3::new(1.0, 0.0, 0.0)
};
let stim = CurlVectorField::new(region);
assert_eq!(stim.at(1.0, Index::new(0, 0, 0)), Fields {
e: Vec3::new(1.0, 0.0, 0.0),
h: Vec3::new(1.0, 0.0, 0.0),
});
}
#[test]
fn curl_stimulus_multi_axis() {
let region = MockRegion {
normal: Vec3::new(0.0, -1.0, 1.0)
};
let stim = CurlVectorField::new(region);
let Fields { e, h } = stim.at(1.0, Index::new(0, 0, 0));
assert_eq!(e, h);
assert!(e.distance(Vec3::new(0.0, -1.0, 1.0).norm()) < 1e-6);
}
}

View File

@ -0,0 +1,123 @@
//! supporting types for basic Stimulus trait/impls
use crate::real::Real;
use crate::cross::vec::Vec3;
/// field densities
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Fields<R> {
pub e: Vec3<R>,
pub h: Vec3<R>,
}
impl<R> Fields<R> {
pub fn new_eh(e: Vec3<R>, h: Vec3<R>) -> Self {
Self { e, h}
}
}
impl<R: Real> Fields<R> {
pub fn e(&self) -> Vec3<R> {
self.e
}
pub fn h(&self) -> Vec3<R> {
self.h
}
pub fn new_e(e: Vec3<R>) -> Self {
Self::new_eh(e, Vec3::zero())
}
pub fn new_h(h: Vec3<R>) -> Self {
Self::new_eh(Vec3::zero(), h)
}
pub fn elem_mul(self, other: FieldMags<R>) -> Fields<R> {
Fields {
e: self.e * other.e,
h: self.h * other.h,
}
}
}
impl<R: Real> std::ops::AddAssign for Fields<R> {
fn add_assign(&mut self, other: Self) {
self.e += other.e;
self.h += other.h;
}
}
impl<R: Real> std::ops::Add for Fields<R> {
type Output = Self;
fn add(mut self, other: Self) -> Self::Output {
self += other;
self
}
}
impl<R: Real> std::ops::Mul<R> for Fields<R> {
type Output = Self;
fn mul(self, scale: R) -> Self::Output {
Fields {
e: self.e * scale,
h: self.h * scale,
}
}
}
/// field magnitude densities (really, signed magnitude)
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct FieldMags<R> {
pub e: R,
pub h: R,
}
impl<R: Real> std::ops::AddAssign for FieldMags<R> {
fn add_assign(&mut self, other: Self) {
self.e += other.e;
self.h += other.h;
}
}
impl<R: Real> std::ops::Add for FieldMags<R> {
type Output = Self;
fn add(mut self, other: Self) -> Self::Output {
self += other;
self
}
}
impl<R: Real> std::ops::Mul<R> for FieldMags<R> {
type Output = Self;
fn mul(self, scale: R) -> Self::Output {
FieldMags {
e: self.e * scale,
h: self.h * scale,
}
}
}
impl<R> FieldMags<R> {
pub fn new_eh(e: R, h: R) -> Self {
Self { e, h }
}
}
impl<R: Real> FieldMags<R> {
pub fn e(&self) -> R {
self.e
}
pub fn h(&self) -> R {
self.h
}
pub fn new_e(e: R) -> Self {
Self::new_eh(e, R::zero())
}
pub fn new_h(h: R) -> Self {
Self::new_eh(R::zero(), h)
}
pub fn elem_mul(self, other: Self) -> Self {
FieldMags {
e: self.e * other.e,
h: self.h * other.h,
}
}
}

View File

@ -0,0 +1,295 @@
use crate::cross::vec::Vec3;
use crate::geom::{Coord as _, Index, Meters};
use crate::real::Real;
use coremem_cross::dim::DimSlice;
use coremem_cross::vec::Vec3u;
use std::borrow::Cow;
use std::ops::Deref;
use rand;
mod fields;
mod time_varying;
mod vector_field;
pub use fields::{Fields, FieldMags};
pub use time_varying::{
Exp,
Gated,
Pulse,
Scaled,
Shifted,
Sinusoid,
StimExt,
Summed,
TimeVarying,
UnitEH,
};
pub use vector_field::{
CurlVectorField,
RegionGated,
VectorField,
};
pub trait Stimulus<R: Real>: Sync {
/// Return the (E, H) field which should be added PER-SECOND to the provided position/time.
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R>;
/// compute the value of this stimulus across all the simulation space
fn rendered<'a>(
&'a self, scale: R, t_sec: R, feature_size: R, dim: Vec3u
) -> Cow<'a, RenderedStimulus<R>> {
Cow::Owned(render_stim(self, scale, t_sec, feature_size, dim))
}
}
fn render_stim<R: Real, S: Stimulus<R> + ?Sized>(
stim: &S, scale: R, t_sec: R, feature_size: R, dim: Vec3u
) -> RenderedStimulus<R> {
let dim_len = dim.product_sum_usize();
let mut e = Vec::new();
e.resize_with(dim_len, Default::default);
let mut h = Vec::new();
h.resize_with(dim_len, Default::default);
rayon::scope(|s| {
let mut undispatched_e = &mut e[..];
let mut undispatched_h = &mut h[..];
for z in 0..dim.z() {
for y in 0..dim.y() {
let (this_e, this_h);
(this_e, undispatched_e) = undispatched_e.split_at_mut(dim.x() as usize);
(this_h, undispatched_h) = undispatched_h.split_at_mut(dim.x() as usize);
s.spawn(move |_| {
for (x, (out_e, out_h)) in this_e.iter_mut().zip(this_h.iter_mut()).enumerate() {
let Fields { e, h } = stim.at(t_sec, feature_size, Index::new(x as u32, y, z));
*out_e = e * scale;
*out_h = h * scale;
}
});
}
}
});
let field_e = DimSlice::new(dim, e);
let field_h = DimSlice::new(dim, h);
RenderedStimulus::new(
field_e, field_h, scale, feature_size, t_sec
)
}
#[derive(Clone)]
pub struct RenderedStimulus<R> {
e: DimSlice<Vec<Vec3<R>>>,
h: DimSlice<Vec<Vec3<R>>>,
scale: R,
feature_size: R,
t_sec: R,
}
impl<R> RenderedStimulus<R> {
pub fn new(
e: DimSlice<Vec<Vec3<R>>>,
h: DimSlice<Vec<Vec3<R>>>,
scale: R,
feature_size: R,
t_sec: R,
) -> Self {
Self { e, h, scale, feature_size, t_sec }
}
pub fn e<'a>(&'a self) -> DimSlice<&'a [Vec3<R>]> {
self.e.as_ref()
}
pub fn h<'a>(&'a self) -> DimSlice<&'a [Vec3<R>]> {
self.h.as_ref()
}
}
impl<R: Real> RenderedStimulus<R> {
pub fn scale(&self) -> R {
self.scale
}
pub fn feature_size(&self) -> R {
self.feature_size
}
pub fn time(&self) -> R {
self.t_sec
}
}
// TODO: is this necessary?
impl<R: Real> VectorField<R> for RenderedStimulus<R> {
fn at(&self, _feat_size: R, loc: Index) -> Fields<R> {
Fields::new_eh(self.e[loc.into()], self.h[loc.into()])
}
}
impl<R: Real> Stimulus<R> for RenderedStimulus<R> {
fn at(&self, _t_sec: R, _feat_size: R, loc: Index) -> Fields<R> {
Fields::new_eh(self.e[loc.into()], self.h[loc.into()])
}
fn rendered<'a>(
&'a self, scale: R, t_sec: R, feature_size: R, dim: Vec3u
) -> Cow<'a, RenderedStimulus<R>> {
if (self.scale, self.t_sec, self.feature_size, self.e.dim()) == (scale, t_sec, feature_size, dim) {
Cow::Borrowed(self)
} else {
Cow::Owned(render_stim(self, scale, t_sec, feature_size, dim))
}
}
}
impl<R: Real> Stimulus<R> for Fields<R> {
fn at(&self, _t_sec: R, _feat_size: R, _loc: Index) -> Fields<R> {
*self
}
}
/// a VectorField type whose amplitude is modulated by a TimeVarying component.
/// users will almost always use this as their stimulus implementation
pub struct ModulatedVectorField<V, T> {
fields: V,
modulation: T,
}
impl<V, T> ModulatedVectorField<V, T> {
pub fn new(fields: V, modulation: T) -> Self {
Self { fields, modulation }
}
pub fn into_inner(self) -> (V, T) {
(self.fields, self.modulation)
}
pub fn fields(&self) -> &V {
&self.fields
}
pub fn modulation(&self) -> &T {
&self.modulation
}
}
impl<R: Real, V: VectorField<R> + Sync, T: TimeVarying<R> + Sync> Stimulus<R> for ModulatedVectorField<V, T> {
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R> {
self.fields.at(feat_size, loc).elem_mul(self.modulation.at(t_sec))
}
}
/// used as a MapVisitor in order to evaluate each Stimulus in a List at a specific time/place.
// struct StimulusEvaluator {
// fields: Fields,
// t_sec: f32,
// feat_size: f32,
// loc: Index,
// }
//
// impl<S: Stimulus> Visitor<&S> for &mut StimulusEvaluator {
// fn visit(&mut self, next: &S) {
// self.fields += next.at(self.t_sec, self.feat_size, self.loc);
// }
// }
//
// impl<L: Sync> Stimulus for L
// where
// for<'a, 'b> &'a L: Visit<&'b mut StimulusEvaluator>,
// {
// fn at(&self, t_sec: f32, pos: Meters) -> Fields {
// let mut ev = StimulusEvaluator { t_sec, pos, fields: Fields::default()};
// self.visit(&mut ev);
// ev.fields
// }
// }
// conflicts with List implementation
// impl<T: Stimulus> Stimulus for &T {
// fn at(&self, t_sec: f32, feat_size: f32, loc: Index) -> Fields {
// (*self).at(t_sec, feat_size, loc)
// }
// }
pub struct StimuliVec<S>(Vec<S>);
pub type DynStimuli<R> = StimuliVec<Box<dyn Stimulus<R> + Send>>;
impl<S> Default for StimuliVec<S> {
fn default() -> Self {
Self(Vec::new())
}
}
impl<S> Deref for StimuliVec<S> {
type Target = Vec<S>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<S> StimuliVec<S> {
pub fn new() -> Self {
Self::default()
}
pub fn from_vec(stim: Vec<S>) -> Self {
Self(stim)
}
pub fn push(&mut self, a: S) {
self.0.push(a)
}
}
impl<R: Real, S: Stimulus<R>> Stimulus<R> for StimuliVec<S> {
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R> {
self.0.iter().map(|i| i.at(t_sec, feat_size, loc))
.fold(Fields::default(), core::ops::Add::add)
}
}
impl<R: Real> Stimulus<R> for Box<dyn Stimulus<R> + Send> {
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R> {
(**self).at(t_sec, feat_size, loc)
}
}
pub struct NoopStimulus;
impl<R: Real> Stimulus<R> for NoopStimulus {
fn at(&self, _t_sec: R, _feat_size: R, _loc: Index) -> Fields<R> {
Fields::default()
}
}
pub struct RngStimulus {
seed: u64,
e_scale: f32,
h_scale: f32,
}
impl RngStimulus {
pub fn new(seed: u64) -> Self {
Self { seed, e_scale: 1e15, h_scale: 1e15 }
}
pub fn new_e(seed: u64) -> Self {
Self { seed, e_scale: 1e15, h_scale: 0.0 }
}
fn gen(&self, t_sec: f32, pos: Meters, scale: f32, salt: u64) -> Vec3<f32> {
use rand::{Rng as _, SeedableRng as _};
let seed = self.seed
^ (t_sec.to_bits() as u64)
^ ((pos.x().to_bits() as u64) << 8)
^ ((pos.y().to_bits() as u64) << 16)
^ ((pos.z().to_bits() as u64) << 24)
^ (salt << 32);
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
Vec3::new(
rng.gen_range(-scale..=scale),
rng.gen_range(-scale..=scale),
rng.gen_range(-scale..=scale),
)
}
}
impl<R: Real> Stimulus<R> for RngStimulus {
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R> {
Fields {
e: self.gen(t_sec.cast(), loc.to_meters(feat_size.cast()), self.e_scale, 0).cast(),
h: self.gen(t_sec.cast(), loc.to_meters(feat_size.cast()), self.h_scale, 0x7de3).cast(),
}
}
}

View File

@ -0,0 +1,254 @@
//! time-varying portions of a Stimulus
use crate::real::{self, Real};
use crate::stim::{Fields, FieldMags, Stimulus};
use crate::geom::Index;
pub trait TimeVarying<R> {
fn at(&self, t_sec: R) -> FieldMags<R>;
}
// TODO: rename to TimeVaryingExt?
pub trait StimExt<R>: Sized {
fn shifted(self, new_start: R) -> Shifted<R, Self> {
Shifted::new(self, new_start)
}
fn gated(self, from: R, to: R) -> Gated<R, Self> {
Gated::new(Pulse::new(from, to), self)
}
fn scaled<T: TimeVarying<R>>(self, scale: T) -> Scaled<Self, T> {
Scaled::new(self, scale)
}
fn summed<T: TimeVarying<R>>(self, with: T) -> Summed<Self, T> {
Summed::new(self, with)
}
}
impl<R, T> StimExt<R> for T {}
impl<R: Real> TimeVarying<R> for FieldMags<R> {
fn at(&self, _t_sec: R) -> FieldMags<R> {
*self
}
}
// assumed to represent the E field
impl<R: Real> TimeVarying<real::Finite<R>> for real::Finite<R> {
fn at(&self, _t_sec: real::Finite<R>) -> FieldMags<real::Finite<R>> {
FieldMags::new_e(*self)
}
}
impl TimeVarying<f32> for f32 {
fn at(&self, _t_sec: f32) -> FieldMags<f32> {
FieldMags::new_e(*self)
}
}
impl TimeVarying<f64> for f64 {
fn at(&self, _t_sec: f64) -> FieldMags<f64> {
FieldMags::new_e(*self)
}
}
// Vec<T> at any `t_sec` behaves as the sum of all its components at that time.
impl<R: Real, T: TimeVarying<R>> TimeVarying<R> for Vec<T> {
fn at(&self, t_sec: R) -> FieldMags<R> {
self.iter().fold(FieldMags::default(), |acc, i| acc + i.at(t_sec))
}
}
pub struct UnitEH;
impl<R: Real> TimeVarying<R> for UnitEH {
fn at(&self, _t_sec: R) -> FieldMags<R> {
FieldMags::new_eh(R::one(), R::one())
}
}
/// E field which changes magnitude sinusoidally as a function of t
#[derive(Clone)]
pub struct Sinusoid<R> {
amp: R,
omega: R,
}
impl<R: Real> Sinusoid<R> {
pub fn new(amp: R, freq: R) -> Self {
Self {
amp,
omega: freq * R::two_pi(),
}
}
pub fn from_wavelength(amp: R, lambda: R) -> Self {
Self::new(amp, lambda.inv())
}
pub fn freq(&self) -> R {
self.omega / R::two_pi()
}
pub fn wavelength(&self) -> R {
self.freq().inv()
}
pub fn one_cycle(self) -> Gated<R, Self> {
let wl = self.wavelength();
self.gated(R::zero(), wl)
}
pub fn half_cycle(self) -> Gated<R, Self> {
let wl = self.wavelength();
self.gated(R::zero(), R::half() * wl)
}
}
impl<R: Real> TimeVarying<R> for Sinusoid<R> {
fn at(&self, t_sec: R) -> FieldMags<R> {
FieldMags {
e: self.amp * (t_sec * self.omega).sin(),
h: R::zero(),
}
}
}
/// E field that decays exponentially over t.
#[derive(Clone)]
pub struct Exp<R> {
tau: R,
}
impl<R: Real + TimeVarying<R>> Exp<R> {
pub fn new(half_life: R) -> Self {
let tau = R::ln2()/half_life;
Self { tau }
}
pub fn new_at(amp: R, start: R, half_life: R) -> Shifted<R, Gated<R, Scaled<Self, R>>> {
Self::new(half_life).scaled(amp).gated(R::zero(), half_life*100f32.cast::<R>()).shifted(start)
}
}
impl<R: Real> TimeVarying<R> for Exp<R> {
fn at(&self, t_sec: R) -> FieldMags<R> {
let a = (t_sec * -self.tau).exp();
FieldMags::new_eh(a, a)
}
}
/// pulses E=1.0 and H=1.0 over the provided duration.
/// this is used as a building block to gate some VectorField over a specific time.
#[derive(Clone)]
pub struct Pulse<R> {
start: R,
end: R,
}
impl<R> Pulse<R> {
pub fn new(start: R, end: R) -> Self {
Self { start, end }
}
}
impl<R: Real> Pulse<R> {
fn contains(&self, t: R) -> bool {
t >= self.start && t < self.end
}
}
impl<R: Real> TimeVarying<R> for Pulse<R> {
fn at(&self, t: R) -> FieldMags<R> {
if self.contains(t) {
FieldMags::new_eh(R::one(), R::one())
} else {
FieldMags::new_eh(R::zero(), R::zero())
}
}
}
pub type Gated<R, T> = Scaled<Pulse<R>, T>;
#[derive(Clone)]
pub struct Shifted<R, T> {
start_at: R,
inner: T,
}
impl<R, T> Shifted<R, T> {
pub fn new(inner: T, start_at: R) -> Self {
Self { inner, start_at }
}
}
impl<R: Real, T: TimeVarying<R>> TimeVarying<R> for Shifted<R, T> {
fn at(&self, t_sec: R) -> FieldMags<R> {
self.inner.at(t_sec - self.start_at)
}
}
// TODO: is this necessary?
impl<R: Real, T: Stimulus<R>> Stimulus<R> for Shifted<R, T> {
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R> {
self.inner.at(t_sec - self.start_at, feat_size, loc)
}
}
#[derive(Clone)]
pub struct Scaled<A, B>(A, B);
impl<A, B> Scaled<A, B> {
pub fn new(a: A, b: B) -> Self {
Self(a, b)
}
}
impl<R: Real, A: TimeVarying<R>, B: TimeVarying<R>> TimeVarying<R> for Scaled<A, B> {
fn at(&self, t_sec: R) -> FieldMags<R> {
self.0.at(t_sec).elem_mul(self.1.at(t_sec))
}
}
#[derive(Clone)]
pub struct Summed<A, B>(A, B);
impl<A, B> Summed<A, B> {
pub fn new(a: A, b: B) -> Self {
Self(a, b)
}
}
impl<R: Real, A: TimeVarying<R>, B: TimeVarying<R>> TimeVarying<R> for Summed<A, B> {
fn at(&self, t_sec: R) -> FieldMags<R> {
self.0.at(t_sec) + self.1.at(t_sec)
}
}
#[cfg(test)]
mod test {
use super::*;
macro_rules! assert_approx_eq {
($x:expr, $e:expr, $h:expr) => {
let x = $x;
let e = $e;
let h = $h;
let diff_e = (x.e - e).abs();
assert!(diff_e <= 0.001, "{:?} != {:?}", x, e);
let diff_h = (x.h - h).abs();
assert!(diff_h <= 0.001, "{:?} != {:?}", x, h);
}
}
#[test]
fn sinusoid() {
let s = Sinusoid::new(10.0, 1000.0);
assert_eq!(s.at(0.0), FieldMags::default());
assert_approx_eq!(s.at(0.00025), 10.0, 0.0);
assert_approx_eq!(s.at(0.00050), 0.0, 0.0);
assert_approx_eq!(s.at(0.00075), -10.0, 0.0);
}
#[test]
fn sinusoid_from_wavelength() {
let s = Sinusoid::from_wavelength(10.0, 0.001);
assert_eq!(s.at(0.0), FieldMags::default());
assert_approx_eq!(s.at(0.00025), 10.0, 0.0);
assert_approx_eq!(s.at(0.00050), 0.0, 0.0);
assert_approx_eq!(s.at(0.00075), -10.0, 0.0);
}
}

View File

@ -0,0 +1,137 @@
use crate::geom::{Coord as _, HasCrossSection, Index, Region};
use crate::real::Real;
use crate::stim::{Fields, Stimulus};
use coremem_cross::dim::DimSlice;
use coremem_cross::vec::Vec3u;
/// a static vector field. different value at each location, but constant in time.
/// often used as a building block by wrapping it in something which modulates the fields over
/// time.
pub trait VectorField<R> {
fn at(&self, feat_size: R, loc: Index) -> Fields<R>;
}
// uniform vector field
impl<R: Real> VectorField<R> for Fields<R> {
fn at(&self, _feat_size: R, _loc: Index) -> Fields<R> {
*self
}
}
// could broaden this and implement directly on T, but blanket impls
// are unwieldy
impl<R: Real, T> VectorField<R> for DimSlice<T>
where
DimSlice<T>: core::ops::Index<Vec3u, Output=Fields<R>>
{
fn at(&self, _feat_size: R, loc: Index) -> Fields<R> {
self[loc.into()]
}
}
/// Apply a time-varying stimulus uniformly across some region
#[derive(Clone)]
pub struct RegionGated<G, S> {
region: G,
stim: S,
}
impl<G, S> RegionGated<G, S> {
pub fn new(region: G, stim: S) -> Self {
Self {
region, stim
}
}
}
// TODO: is this necessary?
impl<R: Real, G: Region + Sync, S: Stimulus<R> + Sync> Stimulus<R> for RegionGated<G, S> {
fn at(&self, t_sec: R, feat_size: R, loc: Index) -> Fields<R> {
if self.region.contains(loc.to_meters(feat_size.cast())) {
self.stim.at(t_sec, feat_size, loc)
} else {
Fields::default()
}
}
}
impl<R: Real, G: Region + Sync, S: VectorField<R>> VectorField<R> for RegionGated<G, S> {
fn at(&self, feat_size: R, loc: Index) -> Fields<R> {
if self.region.contains(loc.to_meters(feat_size.cast())) {
self.stim.at(feat_size, loc)
} else {
Fields::default()
}
}
}
/// apply a stimulus across some region.
/// the stimulus seen at each point is based on its angle about the specified ray.
/// the stimulus has equal E and H vectors. if you want just one, filter it one with `Scaled`.
#[derive(Clone)]
pub struct CurlVectorField<G> {
region: G,
}
impl<G> CurlVectorField<G> {
pub fn new(region: G) -> Self {
Self { region }
}
}
impl<R: Real, G: Region + HasCrossSection> VectorField<R> for CurlVectorField<G> {
fn at(&self, feat_size: R, loc: Index) -> Fields<R> {
let pos = loc.to_meters(feat_size.cast());
if self.region.contains(pos) {
// TODO: do we *want* this to be normalized?
let rotational = self.region.cross_section_normal(pos).norm().cast();
Fields::new_eh(rotational, rotational)
} else {
Fields::default()
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::cross::vec::Vec3;
use crate::geom::Meters;
struct MockRegion {
normal: Vec3<f32>,
}
impl HasCrossSection for MockRegion {
fn cross_section_normal(&self, _p: Meters) -> Vec3<f32> {
self.normal
}
}
impl Region for MockRegion {
fn contains(&self, _p: Meters) -> bool {
true
}
}
#[test]
fn curl_stimulus_trivial() {
let region = MockRegion {
normal: Vec3::new(1.0, 0.0, 0.0)
};
let stim = CurlVectorField::new(region);
assert_eq!(stim.at(1.0, Index::new(0, 0, 0)), Fields {
e: Vec3::new(1.0, 0.0, 0.0),
h: Vec3::new(1.0, 0.0, 0.0),
});
}
#[test]
fn curl_stimulus_multi_axis() {
let region = MockRegion {
normal: Vec3::new(0.0, -1.0, 1.0)
};
let stim = CurlVectorField::new(region);
let Fields { e, h } = stim.at(1.0, Index::new(0, 0, 0));
assert_eq!(e, h);
assert!(e.distance(Vec3::new(0.0, -1.0, 1.0).norm()) < 1e-6);
}
}