stim: convert CurlStimulus to a CurlVectorField and use ModulatedVectorField

this opens the door to caching the vector field stuff.
This commit is contained in:
2022-08-18 20:47:02 -07:00
parent 478db86b75
commit ffda00b796
4 changed files with 106 additions and 111 deletions

View File

@@ -22,7 +22,7 @@ use coremem::real::{R32, Real as _};
use coremem::render::CsvRenderer; use coremem::render::CsvRenderer;
use coremem::sim::spirv::{SpirvSim, WgpuBackend}; use coremem::sim::spirv::{SpirvSim, WgpuBackend};
use coremem::sim::units::{Seconds, Time as _}; use coremem::sim::units::{Seconds, Time as _};
use coremem::stim::{CurlStimulus, Exp1, Gated, Sinusoid, StimExt as _}; use coremem::stim::{CurlVectorField, Exp1, Gated, ModulatedVectorField, Sinusoid, StimExt as _};
use log::{error, info, warn}; use log::{error, info, warn};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -441,25 +441,25 @@ fn run_sim(id: u32, p: Params, g: Geometries) -> Results {
let wave = Sinusoid::from_wavelength(amp, duration * 2.0) let wave = Sinusoid::from_wavelength(amp, duration * 2.0)
.half_cycle() .half_cycle()
.shifted(start); .shifted(start);
driver.add_stimulus(CurlStimulus::new( driver.add_stimulus(ModulatedVectorField::new(
region.clone(), CurlVectorField::new(region.clone()),
wave.clone(), wave,
)); ));
}; };
let add_drive_square_pulse = |driver: &mut Driver<_>, region: &Torus, start: f32, duration: f32, amp: f32| { let add_drive_square_pulse = |driver: &mut Driver<_>, region: &Torus, start: f32, duration: f32, amp: f32| {
let wave = Gated::new(amp, start, start+duration); let wave = Gated::new(amp, start, start+duration);
driver.add_stimulus(CurlStimulus::new( driver.add_stimulus(ModulatedVectorField::new(
region.clone(), CurlVectorField::new(region.clone()),
wave.clone(), wave,
)); ));
}; };
let add_drive_exp_pulse = |driver: &mut Driver<_>, region: &Torus, start: f32, duration: f32, amp: f32| { let add_drive_exp_pulse = |driver: &mut Driver<_>, region: &Torus, start: f32, duration: f32, amp: f32| {
let wave = Exp1::new_at(amp, start, 0.5*duration); let wave = Exp1::new_at(amp, start, 0.5*duration);
driver.add_stimulus(CurlStimulus::new( driver.add_stimulus(ModulatedVectorField::new(
region.clone(), CurlVectorField::new(region.clone()),
wave.clone(), wave,
)); ));
}; };

View File

@@ -43,7 +43,7 @@ use coremem::meas;
use coremem::real::{self, Real as _}; use coremem::real::{self, Real as _};
use coremem::sim::spirv::{self, SpirvSim}; use coremem::sim::spirv::{self, SpirvSim};
use coremem::sim::units::Seconds; use coremem::sim::units::Seconds;
use coremem::stim::{Stimulus, CurlStimulus, DynStimuli, Exp, Gated, Shifted}; use coremem::stim::{CurlVectorField, Exp, Gated, ModulatedVectorField, Shifted, StimExt as _, StimuliVec};
use coremem::Driver; use coremem::Driver;
type R = real::R32; type R = real::R32;
@@ -76,25 +76,23 @@ enum ClockState {
ReleaseLow, ReleaseLow,
Float, Float,
} }
type ClockSegment = Shifted<Gated<Exp<f32>>>;
impl ClockState { impl ClockState {
fn stimulus(&self, params: &Params, cycle: u32, ctl: u32) fn time_stimulus(&self, params: &Params, cycle: u32)
-> Option<Box<dyn Stimulus>> -> Option<ClockSegment>
{ {
use ClockState::*; use ClockState::*;
match self { match self {
HoldHigh => Some(Box::new(params.control_signal_hold(cycle, ctl, DriveDirection::High))), HoldHigh => Some(params.control_signal_hold(cycle, DriveDirection::High)),
ReleaseHigh => Some(Box::new(params.control_signal_release(cycle, ctl, DriveDirection::High))), ReleaseHigh => Some(params.control_signal_release(cycle, DriveDirection::High)),
HoldLow => Some(Box::new(params.control_signal_hold(cycle, ctl, DriveDirection::Low))), HoldLow => Some(params.control_signal_hold(cycle, DriveDirection::Low)),
ReleaseLow => Some(Box::new(params.control_signal_release(cycle, ctl, DriveDirection::Low))), ReleaseLow => Some(params.control_signal_release(cycle, DriveDirection::Low)),
Float => None, Float => None,
} }
} }
} }
type HoldStim = Gated<CurlStimulus<Torus, f32>>;
type ReleaseStim = Shifted<Gated<Exp<CurlStimulus<Torus, f32>>>>;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
struct Params { struct Params {
input_magnitude: f32, input_magnitude: f32,
@@ -146,30 +144,18 @@ impl Params {
Torus::new_xz(Meters::new(self.couplingx(n), self.sy, self.sz), self.coupling_major, self.coupling_minor) Torus::new_xz(Meters::new(self.couplingx(n), self.sy, self.sz), self.coupling_major, self.coupling_minor)
} }
fn control_signal_hold(&self, cycle: u32, core: u32, ty: DriveDirection) -> HoldStim { fn control_signal_hold(&self, cycle: u32, ty: DriveDirection) -> ClockSegment {
let region = self.ctl(core);
let area = region.cross_section();
let amp = ty.as_signed_f32() * self.input_magnitude / area;
let base_stim = CurlStimulus::new(
region.clone(),
amp,
);
// simple square wave: // simple square wave:
let amp = ty.as_signed_f32() * self.input_magnitude;
let start = self.clock_phase_duration * cycle as f32; let start = self.clock_phase_duration * cycle as f32;
Gated::new(base_stim, start, start + self.clock_phase_duration) Exp::new_at(amp, start, 100.0 /* very long decay */)
} }
fn control_signal_release(&self, cycle: u32, core: u32, ty: DriveDirection) -> ReleaseStim { fn control_signal_release(&self, cycle: u32, ty: DriveDirection) -> ClockSegment {
let region = self.ctl(core);
let area = region.cross_section();
let amp = ty.as_signed_f32() * self.input_magnitude / area;
let base_stim = CurlStimulus::new(
region.clone(),
amp,
);
// decaying exponential wave: // decaying exponential wave:
let amp = ty.as_signed_f32() * self.input_magnitude;
let start = self.clock_phase_duration * cycle as f32; let start = self.clock_phase_duration * cycle as f32;
Exp::new_at(base_stim, start, self.clock_decay) Exp::new_at(amp, start, self.clock_decay)
} }
} }
@@ -245,21 +231,24 @@ fn main() {
]; ];
let num_cycles = drive_map.len() as u32; let num_cycles = drive_map.len() as u32;
let num_cores = drive_map[0].len() as u32; let num_cores = drive_map[0].len() as u32;
let stim: Vec<_> = drive_map.into_iter() let mut core_drivers = vec![Vec::default(); num_cores as usize];
for (cycle, cores) in drive_map.into_iter().enumerate() {
for (core, clock) in cores.into_iter().enumerate() {
core_drivers[core as usize].extend(clock.time_stimulus(&params, cycle as u32));
}
}
let stim: Vec<_> = core_drivers.into_iter()
.enumerate() .enumerate()
.flat_map(|(cycle, cores)| { .map(|(core, time_varying)| {
cores.into_iter() let region = params.ctl(core as u32);
.enumerate() let area = region.cross_section();
.flat_map(move |(core, clock_state)| { let amp = 1.0 / area;
clock_state.stimulus(&params, cycle as u32, core as u32) let v_field = CurlVectorField::new(region.clone());
}) ModulatedVectorField::new(v_field, time_varying.scaled(amp))
}) })
.collect(); .collect();
// temporary sanity checks assert_eq!(stim.len(), 5);
assert_eq!(num_cycles, 14); let stim = StimuliVec::from_vec(stim);
assert_eq!(num_cores, 5);
assert_eq!(stim.len(), 14*5 - 12);
let stim = DynStimuli::from_vec(stim);
let wire_mat = IsomorphicConductor::new(1e6f32.cast::<R>()); let wire_mat = IsomorphicConductor::new(1e6f32.cast::<R>());

View File

@@ -7,7 +7,7 @@ use coremem::{Driver, mat, meas};
use coremem::geom::{Coord as _, Meters, Torus}; use coremem::geom::{Coord as _, Meters, Torus};
use coremem::sim::spirv::{SpirvSim, WgpuBackend}; use coremem::sim::spirv::{SpirvSim, WgpuBackend};
use coremem::sim::units::Seconds; use coremem::sim::units::Seconds;
use coremem::stim::{CurlStimulus, Sinusoid, StimExt as _}; use coremem::stim::{CurlVectorField, ModulatedVectorField, Sinusoid, StimExt as _};
fn main() { fn main() {
@@ -81,9 +81,9 @@ fn main() {
let wave = Sinusoid::from_wavelength(amp, duration * 2.0) let wave = Sinusoid::from_wavelength(amp, duration * 2.0)
.half_cycle() .half_cycle()
.shifted(start); .shifted(start);
driver.add_stimulus(CurlStimulus::new( driver.add_stimulus(ModulatedVectorField::new(
region.clone(), CurlVectorField::new(region.clone()),
wave.clone(), wave,
)); ));
}; };

View File

@@ -217,22 +217,28 @@ where
// } // }
/// generic stimuli collection which monomorphizes everything by boxing it. pub struct StimuliVec<S>(Vec<S>);
#[derive(Default)]
pub struct DynStimuli(Vec<Box<dyn Stimulus>>);
impl DynStimuli { pub type DynStimuli = StimuliVec<Box<dyn Stimulus>>;
impl<S> Default for StimuliVec<S> {
fn default() -> Self {
Self(Vec::new())
}
}
impl<S> StimuliVec<S> {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self::default()
} }
pub fn from_vec(stim: Vec<Box<dyn Stimulus>>) -> Self { pub fn from_vec(stim: Vec<S>) -> Self {
Self(stim) Self(stim)
} }
pub fn push(&mut self, a: Box<dyn Stimulus>) { pub fn push(&mut self, a: S) {
self.0.push(a) self.0.push(a)
} }
} }
impl Stimulus for DynStimuli { impl<S: Stimulus> Stimulus for StimuliVec<S> {
fn at(&self, t_sec: f32, feat_size: f32, loc: Index) -> Fields { fn at(&self, t_sec: f32, feat_size: f32, loc: Index) -> Fields {
self.0.iter().map(|i| i.at(t_sec, feat_size, loc)) self.0.iter().map(|i| i.at(t_sec, feat_size, loc))
.fold(Fields::default(), core::ops::Add::add) .fold(Fields::default(), core::ops::Add::add)
@@ -243,12 +249,12 @@ impl Stimulus for DynStimuli {
} }
} }
} }
//
// impl Stimulus for Box<dyn Stimulus> { impl Stimulus for Box<dyn Stimulus> {
// fn at(&self, t_sec: f32, pos: Meters) -> Fields { fn at(&self, t_sec: f32, feat_size: f32, loc: Index) -> Fields {
// (**self).at(t_sec, pos) (**self).at(t_sec, feat_size, loc)
// } }
// } }
pub struct NoopStimulus; pub struct NoopStimulus;
impl Stimulus for NoopStimulus { impl Stimulus for NoopStimulus {
@@ -331,32 +337,27 @@ impl<R: Region + Sync, S: VectorField> VectorField for RegionGated<R, S> {
} }
} }
/// Apply a time-varying stimulus across some region. /// apply a stimulus across some region.
/// The stimulus seen at each point is based on its angle about the specified ray. /// 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)] #[derive(Clone)]
pub struct CurlStimulus<R, T> { pub struct CurlVectorField<R> {
region: R, region: R
stim: T,
} }
impl<R, T> CurlStimulus<R, T> { impl<R> CurlVectorField<R> {
pub fn new(region: R, stim: T) -> Self { pub fn new(region: R) -> Self {
Self { region, stim } Self { region }
} }
} }
impl<R: Region + HasCrossSection, T: TimeVarying + Sync> Stimulus for CurlStimulus<R, T> { impl<R: Region + HasCrossSection> VectorField for CurlVectorField<R> {
fn at(&self, t_sec: f32, feat_size: f32, loc: Index) -> Fields { fn at(&self, feat_size: f32, loc: Index) -> Fields {
let pos = loc.to_meters(feat_size); let pos = loc.to_meters(feat_size);
if self.region.contains(pos) { if self.region.contains(pos) {
let FieldMags { e, h } = self.stim.at(t_sec); // TODO: do we *want* this to be normalized?
let rotational = self.region.cross_section_normal(pos); let rotational = self.region.cross_section_normal(pos).norm();
let impulse_e = rotational.with_mag(e.cast()).unwrap_or_default(); Fields::new_eh(rotational, rotational)
let impulse_h = rotational.with_mag(h.cast()).unwrap_or_default();
Fields {
e: impulse_e,
h: impulse_h,
}
} else { } else {
Fields::default() Fields::default()
} }
@@ -370,6 +371,9 @@ pub trait StimExt: Sized {
fn gated(self, from: f32, to: f32) -> Gated<Self> { fn gated(self, from: f32, to: f32) -> Gated<Self> {
Gated::new(self, from, to) Gated::new(self, from, to)
} }
fn scaled<T: TimeVarying>(self, scale: T) -> Scaled<Self, T> {
Scaled::new(self, scale)
}
} }
impl<T> StimExt for T {} impl<T> StimExt for T {}
@@ -386,6 +390,12 @@ impl TimeVarying for f32 {
} }
} }
impl<T: TimeVarying> TimeVarying for Vec<T> {
fn at(&self, t_sec: f32) -> FieldMags {
self.iter().fold(FieldMags::default(), |acc, i| acc + i.at(t_sec))
}
}
/// E field which changes magnitude sinusoidally as a function of t /// E field which changes magnitude sinusoidally as a function of t
#[derive(Clone)] #[derive(Clone)]
pub struct Sinusoid { pub struct Sinusoid {
@@ -568,6 +578,21 @@ impl<T: Stimulus> Stimulus for Shifted<T> {
} }
} }
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<A: TimeVarying, B: TimeVarying> TimeVarying for Scaled<A, B> {
fn at(&self, t_sec: f32) -> FieldMags {
self.0.at(t_sec).elem_mul(self.1.at(t_sec))
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@@ -621,26 +646,10 @@ mod test {
let region = MockRegion { let region = MockRegion {
normal: Vec3::new(1.0, 0.0, 0.0) normal: Vec3::new(1.0, 0.0, 0.0)
}; };
// stim acts as e: 1.0, h: 0.0 let stim = CurlVectorField::new(region);
let stim = CurlStimulus::new(region, 1.0); assert_eq!(stim.at(1.0, Index::new(0, 0, 0)), Fields {
assert_eq!(stim.at(0.0, 1.0, Index::new(0, 0, 0)), Fields {
e: Vec3::new(1.0, 0.0, 0.0), e: Vec3::new(1.0, 0.0, 0.0),
h: Vec3::new(0.0, 0.0, 0.0), h: Vec3::new(1.0, 0.0, 0.0),
});
}
#[test]
fn curl_stimulus_scales_right() {
let region = MockRegion {
// the magnitude of this normal shouldn't influence the curl.
// though, maybe it would be more useful if it *did*?
normal: Vec3::new(5.0, 0.0, 0.0)
};
// stim acts as e: -3.0, h: 0.0
let stim = CurlStimulus::new(region, -3.0);
assert_eq!(stim.at(0.0, 1.0, Index::new(0, 0, 0)), Fields {
e: Vec3::new(-3.0, 0.0, 0.0),
h: Vec3::new(0.0, 0.0, 0.0),
}); });
} }
@@ -649,12 +658,9 @@ mod test {
let region = MockRegion { let region = MockRegion {
normal: Vec3::new(0.0, -1.0, 1.0) normal: Vec3::new(0.0, -1.0, 1.0)
}; };
// stim acts as e: 2.0, h: 0.0 let stim = CurlVectorField::new(region);
let stim = CurlStimulus::new(region, 2.0); let Fields { e, h } = stim.at(1.0, Index::new(0, 0, 0));
let Fields { e, h: _ } = stim.at(0.0, 1.0, Index::new(0, 0, 0)); assert_eq!(e, h);
assert!(e.distance( assert!(e.distance(Vec3::new(0.0, -1.0, 1.0).norm()) < 1e-6);
Vec3::new(0.0, -1.0, 1.0).with_mag(2.0).unwrap()
) < 1e-6
);
} }
} }