stim: convert CurlStimulus to a CurlVectorField and use ModulatedVectorField
this opens the door to caching the vector field stuff.
This commit is contained in:
@@ -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,
|
||||
));
|
||||
};
|
||||
|
||||
|
@@ -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(¶ms, 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(¶ms, 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>());
|
||||
|
@@ -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,
|
||||
));
|
||||
};
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user