fdtd-coremem/crates/coremem/src/stim/mod.rs

296 lines
8.0 KiB
Rust

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,
Summed,
TimeVarying,
TimeVaryingExt,
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(),
}
}
}