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::sim::spirv::{SpirvSim, WgpuBackend};
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 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)
.half_cycle()
.shifted(start);
driver.add_stimulus(CurlStimulus::new(
region.clone(),
wave.clone(),
driver.add_stimulus(ModulatedVectorField::new(
CurlVectorField::new(region.clone()),
wave,
));
};
let add_drive_square_pulse = |driver: &mut Driver<_>, region: &Torus, start: f32, duration: f32, amp: f32| {
let wave = Gated::new(amp, start, start+duration);
driver.add_stimulus(CurlStimulus::new(
region.clone(),
wave.clone(),
driver.add_stimulus(ModulatedVectorField::new(
CurlVectorField::new(region.clone()),
wave,
));
};
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);
driver.add_stimulus(CurlStimulus::new(
region.clone(),
wave.clone(),
driver.add_stimulus(ModulatedVectorField::new(
CurlVectorField::new(region.clone()),
wave,
));
};

View File

@@ -43,7 +43,7 @@ use coremem::meas;
use coremem::real::{self, Real as _};
use coremem::sim::spirv::{self, SpirvSim};
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;
type R = real::R32;
@@ -76,25 +76,23 @@ enum ClockState {
ReleaseLow,
Float,
}
type ClockSegment = Shifted<Gated<Exp<f32>>>;
impl ClockState {
fn stimulus(&self, params: &Params, cycle: u32, ctl: u32)
-> Option<Box<dyn Stimulus>>
fn time_stimulus(&self, params: &Params, cycle: u32)
-> Option<ClockSegment>
{
use ClockState::*;
match self {
HoldHigh => Some(Box::new(params.control_signal_hold(cycle, ctl, DriveDirection::High))),
ReleaseHigh => Some(Box::new(params.control_signal_release(cycle, ctl, DriveDirection::High))),
HoldLow => Some(Box::new(params.control_signal_hold(cycle, ctl, DriveDirection::Low))),
ReleaseLow => Some(Box::new(params.control_signal_release(cycle, ctl, DriveDirection::Low))),
HoldHigh => Some(params.control_signal_hold(cycle, DriveDirection::High)),
ReleaseHigh => Some(params.control_signal_release(cycle, DriveDirection::High)),
HoldLow => Some(params.control_signal_hold(cycle, DriveDirection::Low)),
ReleaseLow => Some(params.control_signal_release(cycle, DriveDirection::Low)),
Float => None,
}
}
}
type HoldStim = Gated<CurlStimulus<Torus, f32>>;
type ReleaseStim = Shifted<Gated<Exp<CurlStimulus<Torus, f32>>>>;
#[derive(Copy, Clone)]
struct Params {
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)
}
fn control_signal_hold(&self, cycle: u32, core: u32, ty: DriveDirection) -> HoldStim {
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,
);
fn control_signal_hold(&self, cycle: u32, ty: DriveDirection) -> ClockSegment {
// simple square wave:
let amp = ty.as_signed_f32() * self.input_magnitude;
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 {
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,
);
fn control_signal_release(&self, cycle: u32, ty: DriveDirection) -> ClockSegment {
// decaying exponential wave:
let amp = ty.as_signed_f32() * self.input_magnitude;
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_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()
.flat_map(|(cycle, cores)| {
cores.into_iter()
.enumerate()
.flat_map(move |(core, clock_state)| {
clock_state.stimulus(&params, cycle as u32, core as u32)
})
.map(|(core, time_varying)| {
let region = params.ctl(core as u32);
let area = region.cross_section();
let amp = 1.0 / area;
let v_field = CurlVectorField::new(region.clone());
ModulatedVectorField::new(v_field, time_varying.scaled(amp))
})
.collect();
// temporary sanity checks
assert_eq!(num_cycles, 14);
assert_eq!(num_cores, 5);
assert_eq!(stim.len(), 14*5 - 12);
let stim = DynStimuli::from_vec(stim);
assert_eq!(stim.len(), 5);
let stim = StimuliVec::from_vec(stim);
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::sim::spirv::{SpirvSim, WgpuBackend};
use coremem::sim::units::Seconds;
use coremem::stim::{CurlStimulus, Sinusoid, StimExt as _};
use coremem::stim::{CurlVectorField, ModulatedVectorField, Sinusoid, StimExt as _};
fn main() {
@@ -81,9 +81,9 @@ fn main() {
let wave = Sinusoid::from_wavelength(amp, duration * 2.0)
.half_cycle()
.shifted(start);
driver.add_stimulus(CurlStimulus::new(
region.clone(),
wave.clone(),
driver.add_stimulus(ModulatedVectorField::new(
CurlVectorField::new(region.clone()),
wave,
));
};

View File

@@ -217,22 +217,28 @@ where
// }
/// generic stimuli collection which monomorphizes everything by boxing it.
#[derive(Default)]
pub struct DynStimuli(Vec<Box<dyn Stimulus>>);
pub struct StimuliVec<S>(Vec<S>);
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 {
Self::default()
}
pub fn from_vec(stim: Vec<Box<dyn Stimulus>>) -> Self {
pub fn from_vec(stim: Vec<S>) -> Self {
Self(stim)
}
pub fn push(&mut self, a: Box<dyn Stimulus>) {
pub fn push(&mut self, a: S) {
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 {
self.0.iter().map(|i| i.at(t_sec, feat_size, loc))
.fold(Fields::default(), core::ops::Add::add)
@@ -243,12 +249,12 @@ impl Stimulus for DynStimuli {
}
}
}
//
// impl Stimulus for Box<dyn Stimulus> {
// fn at(&self, t_sec: f32, pos: Meters) -> Fields {
// (**self).at(t_sec, pos)
// }
// }
impl Stimulus for Box<dyn Stimulus> {
fn at(&self, t_sec: f32, feat_size: f32, loc: Index) -> Fields {
(**self).at(t_sec, feat_size, loc)
}
}
pub struct 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.
/// The stimulus seen at each point is based on its angle about the specified ray.
/// 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 CurlStimulus<R, T> {
region: R,
stim: T,
pub struct CurlVectorField<R> {
region: R
}
impl<R, T> CurlStimulus<R, T> {
pub fn new(region: R, stim: T) -> Self {
Self { region, stim }
impl<R> CurlVectorField<R> {
pub fn new(region: R) -> Self {
Self { region }
}
}
impl<R: Region + HasCrossSection, T: TimeVarying + Sync> Stimulus for CurlStimulus<R, T> {
fn at(&self, t_sec: f32, feat_size: f32, loc: Index) -> Fields {
impl<R: Region + HasCrossSection> VectorField for CurlVectorField<R> {
fn at(&self, feat_size: f32, loc: Index) -> Fields {
let pos = loc.to_meters(feat_size);
if self.region.contains(pos) {
let FieldMags { e, h } = self.stim.at(t_sec);
let rotational = self.region.cross_section_normal(pos);
let impulse_e = rotational.with_mag(e.cast()).unwrap_or_default();
let impulse_h = rotational.with_mag(h.cast()).unwrap_or_default();
Fields {
e: impulse_e,
h: impulse_h,
}
// TODO: do we *want* this to be normalized?
let rotational = self.region.cross_section_normal(pos).norm();
Fields::new_eh(rotational, rotational)
} else {
Fields::default()
}
@@ -370,6 +371,9 @@ pub trait StimExt: Sized {
fn gated(self, from: f32, to: f32) -> Gated<Self> {
Gated::new(self, from, to)
}
fn scaled<T: TimeVarying>(self, scale: T) -> Scaled<Self, T> {
Scaled::new(self, scale)
}
}
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
#[derive(Clone)]
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)]
mod test {
use super::*;
@@ -621,26 +646,10 @@ mod test {
let region = MockRegion {
normal: Vec3::new(1.0, 0.0, 0.0)
};
// stim acts as e: 1.0, h: 0.0
let stim = CurlStimulus::new(region, 1.0);
assert_eq!(stim.at(0.0, 1.0, Index::new(0, 0, 0)), Fields {
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(0.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),
h: Vec3::new(1.0, 0.0, 0.0),
});
}
@@ -649,12 +658,9 @@ mod test {
let region = MockRegion {
normal: Vec3::new(0.0, -1.0, 1.0)
};
// stim acts as e: 2.0, h: 0.0
let stim = CurlStimulus::new(region, 2.0);
let Fields { e, h: _ } = stim.at(0.0, 1.0, Index::new(0, 0, 0));
assert!(e.distance(
Vec3::new(0.0, -1.0, 1.0).with_mag(2.0).unwrap()
) < 1e-6
);
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);
}
}