diff --git a/examples/buffer_proto5.rs b/examples/buffer_proto5.rs index db028a3..8b49647 100644 --- a/examples/buffer_proto5.rs +++ b/examples/buffer_proto5.rs @@ -2,10 +2,13 @@ //! v.s. the fourth prototype, it changes the couplings in an attempt to reduce unwanted //! clock -> mem2 coupling -use coremem::{Driver, mat, meas, SampleableSim as _, SpirvDriver}; -use coremem::real::R32 as Real; -use coremem::geom::{region, Cube, Dilate, Index, Meters, Spiral, SwapYZ, Torus, Translate, Wrap}; +use coremem::{Driver, mat, meas, SpirvDriver}; +use coremem::geom::{region, Cube, Dilate, Meters, Region, Spiral, SwapYZ, Torus, Translate, Wrap}; use coremem::stim::{CurlStimulus, Gated, Sinusoid1, TimeVarying1 as _}; +use log::info; + +#[allow(unused)] +use coremem::geom::{Coord as _, Region as _}; #[derive(Copy, Clone, Debug)] struct Params { @@ -108,23 +111,27 @@ fn run_sim(id: u32, p: Params) { ferro_center + Meters::new_y(p.ferro_major + 4.0*p.wire_wrap_minor + 12.0*p.feat_size), Meters::new(p.ferro_buffer + 2.0*p.ferro_major + 4.0*p.feat_size, 2.0*p.feat_size, 2.0*p.feat_size) ); + let coupling_stub_top_left = Cube::new_including_negatives( + coupling_wire_top.bot_left_out(), + coupling_wire_top.bot_left_out() + Meters::new(4.0*p.feat_size, 10.0*p.feat_size, 2.0*p.feat_size) + ); + let coupling_stub_top_right = Cube::new_including_negatives( + coupling_wire_top.bot_right_out(), + coupling_wire_top.bot_right_out() + Meters::new(-4.0*p.feat_size, 10.0*p.feat_size, 2.0*p.feat_size) + ); + let coupling_stub_bot_left = Cube::new_including_negatives( + coupling_wire_bot.top_left_out(), + coupling_wire_bot.top_left_out() + Meters::new(4.0*p.feat_size, -10.0*p.feat_size, 2.0*p.feat_size) + ); + let coupling_stub_bot_right = Cube::new_including_negatives( + coupling_wire_bot.top_right_out(), + coupling_wire_bot.top_right_out() + Meters::new(-4.0*p.feat_size, -10.0*p.feat_size, 2.0*p.feat_size) + ); let coupling_stubs = region::Union::new() - .with(Cube::new_including_negatives( - coupling_wire_top.bot_left_out(), - coupling_wire_top.bot_left_out() + Meters::new(4.0*p.feat_size, 10.0*p.feat_size, 2.0*p.feat_size) - )) - .with(Cube::new_including_negatives( - coupling_wire_top.bot_right_out(), - coupling_wire_top.bot_right_out() + Meters::new(-4.0*p.feat_size, 10.0*p.feat_size, 2.0*p.feat_size) - )) - .with(Cube::new_including_negatives( - coupling_wire_bot.top_left_out(), - coupling_wire_bot.top_left_out() + Meters::new(4.0*p.feat_size, -10.0*p.feat_size, 2.0*p.feat_size) - )) - .with(Cube::new_including_negatives( - coupling_wire_bot.top_right_out(), - coupling_wire_bot.top_right_out() + Meters::new(-4.0*p.feat_size, -10.0*p.feat_size, 2.0*p.feat_size) - )) + .with(coupling_stub_top_left.clone()) + .with(coupling_stub_top_right.clone()) + .with(coupling_stub_bot_left.clone()) + .with(coupling_stub_bot_right.clone()) ; let coupling_wires = region::Union::new() @@ -160,6 +167,33 @@ fn run_sim(id: u32, p: Params) { p.feat_size, ), "{:?}", p); + assert!(wrap1_with_coupling.contains( + coupling_stub_top_left.center().to_index(p.feat_size).to_meters(p.feat_size), + )); + assert!(wrap1_with_coupling.contains( + coupling_stub_bot_left.center().to_index(p.feat_size).to_meters(p.feat_size), + )); + assert!(wrap2_with_coupling.contains( + coupling_stub_top_right.center().to_index(p.feat_size).to_meters(p.feat_size), + )); + assert!(wrap2_with_coupling.contains( + coupling_stub_bot_right.center().to_index(p.feat_size).to_meters(p.feat_size), + )); + info!("wrap1 length: {}", region::distance_to( + &wrap1_with_coupling, + coupling_stub_top_left.center().to_index(p.feat_size), + coupling_stub_bot_left.center().to_index(p.feat_size), + p.feat_size, + ).unwrap()); + info!("wrap2 length: {}", region::distance_to( + &wrap2_with_coupling, + coupling_stub_top_right.center().to_index(p.feat_size), + coupling_stub_bot_right.center().to_index(p.feat_size), + p.feat_size, + ).unwrap()); + + return; + // mu_r=881.33, starting at H=25 to H=75. let ferro_mat = mat::MHPgram::new(25.0, 881.33, 44000.0); // let ferro_mat = mat::db::conductor(wire_conductivity); @@ -173,10 +207,10 @@ fn run_sim(id: u32, p: Params) { driver.fill_region(&set2_region, wire_mat); driver.fill_region(&coupling_region, wire_mat); - println!("boundary: {}um; {}um", m_to_um(p.boundary_xy), m_to_um(p.boundary_z)); - println!("size: {}, {}, {}", width, height, depth); - println!("ferro1: {:?}", ferro1_center); - println!("ferro2: {:?}", ferro2_center); + info!("boundary: {}um; {}um", m_to_um(p.boundary_xy), m_to_um(p.boundary_z)); + info!("size: {}, {}, {}", width, height, depth); + info!("ferro1: {:?}", ferro1_center); + info!("ferro2: {:?}", ferro2_center); driver.add_classical_boundary(Meters::new(p.boundary_xy, p.boundary_xy, p.boundary_z)); assert!(driver.test_region_filled(&ferro1_region, ferro_mat)); @@ -271,15 +305,20 @@ fn run_sim(id: u32, p: Params) { fn main() { coremem::init_logging(); for (i, dump_frames, wraps1, wraps2) in [ - (38, Some(1000000), 20.0, 12.0), - (39, Some(1000000), 20.0, 8.0), - (40, Some(1000000), 20.0, 16.0), - (41, Some(1000000), 20.0, 20.0), - // // (42, Some(1000000), 24.0, 12.0), + // (38, Some(1000000), 20.0, 12.0), + // (39, Some(1000000), 20.0, 8.0), + // (51, Some(1000000), 16.0, 16.0), + // (41, Some(1000000), 20.0, 20.0), + // // (42, Some(1000000), 24.0, 12.0), // FAILS auto connection check // // (43, Some(1000000), 28.0, 12.0), // // (44, Some(1000000), 32.0, 12.0), // // (45, Some(1000000), 24.0, 16.0), // // (46, Some(1000000), 24.0, 20.0), + (47, Some(1000000), 24.0, 16.0), + (48, Some(1000000), 24.0, 20.0), // passes auto connection check + (49, Some(1000000), 28.0, 16.0), // passes auto connection check + (50, Some(1000000), 28.0, 20.0), // passes auto connection check + // // (51, Some(1000000), 32.0, 16.0), // FAILS auto connection check ].iter().copied() { run_sim(i, Params { feat_size: 40e-6f32, @@ -289,7 +328,7 @@ fn main() { boundary_xy: 320e-6, boundary_z: 320e-6, - ferro_major: 1240e-6, + ferro_major: 1360e-6, ferro_minor: 60e-6, ferro_buffer: 1320e-6, wire_minor: 40e-6, diff --git a/src/driver.rs b/src/driver.rs index abfb408..71a22a1 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -34,13 +34,15 @@ pub struct Driver { sim_end_time: Option, } +pub type SpirvDriver = Driver; + impl Driver> { pub fn new(size: C, feature_size: f32) -> Self { Self::new_with_state(SimState::new(size.to_index(feature_size), feature_size)) } } -impl Driver { +impl SpirvDriver { pub fn new_spirv(size: C, feature_size: f32) -> Self { Self::new_with_state(SpirvSim::new(size.to_index(feature_size), feature_size)) } @@ -107,31 +109,31 @@ impl Driver { } fn add_renderer + 'static>( - &mut self, renderer: Rend, name: &str, step_frequency: u64 + &mut self, renderer: Rend, name: &str, step_frequency: u64, frame_limit: Option ) { - info!("render to {} at f={}", name, step_frequency); - self.renderer.push(renderer, step_frequency); + info!("render to {} at f={} until f={:?}", name, step_frequency, frame_limit); + self.renderer.push(renderer, step_frequency, frame_limit); } - pub fn add_y4m_renderer>(&mut self, output: P, step_frequency: u64) { + pub fn add_y4m_renderer>(&mut self, output: P, step_frequency: u64, frame_limit: Option) { let output = output.into(); let name = output.to_string_lossy().into_owned(); - self.add_renderer(render::Y4MRenderer::new(output), &*name, step_frequency); + self.add_renderer(render::Y4MRenderer::new(output), &*name, step_frequency, frame_limit); } - pub fn add_term_renderer(&mut self, step_frequency: u64) { - self.add_renderer(render::ColorTermRenderer, "terminal", step_frequency); + pub fn add_term_renderer(&mut self, step_frequency: u64, frame_limit: Option) { + self.add_renderer(render::ColorTermRenderer, "terminal", step_frequency, frame_limit); } - pub fn add_csv_renderer(&mut self, path: &str, step_frequency: u64) { - self.add_renderer(render::CsvRenderer::new(path), path, step_frequency); + pub fn add_csv_renderer(&mut self, path: &str, step_frequency: u64, frame_limit: Option) { + self.add_renderer(render::CsvRenderer::new(path), path, step_frequency, frame_limit); } } impl Driver { - pub fn add_serializer_renderer(&mut self, out_base: &str, step_frequency: u64) { + pub fn add_serializer_renderer(&mut self, out_base: &str, step_frequency: u64, frame_limit: Option) { let fmt_str = format!("{out_base}{{step_no}}.bc", out_base=out_base); - self.add_renderer(render::SerializerRenderer::new_static(&*fmt_str), &*fmt_str, step_frequency); + self.add_renderer(render::SerializerRenderer::new_static(&*fmt_str), &*fmt_str, step_frequency, frame_limit); } } @@ -141,7 +143,7 @@ impl Deserialize<'a> + 'sta if let Some(state) = ser.try_load() { self.state = state.state; } - self.add_renderer(ser, state_file, snapshot_frequency); + self.add_renderer(ser, state_file, snapshot_frequency, None); } } diff --git a/src/geom/mod.rs b/src/geom/mod.rs index a4fae57..93e5e21 100644 --- a/src/geom/mod.rs +++ b/src/geom/mod.rs @@ -8,7 +8,7 @@ mod vecu; pub use line::Line2d; pub use polygon::Polygon2d; pub use region::{ - Cube, CylinderZ, InvertedRegion, Region, Sphere, Spiral, SwapXZ, SwapYZ, Torus, Translate, Union, WorldRegion, Wrap + Cube, CylinderZ, Dilate, InvertedRegion, Region, Sphere, Spiral, SwapXZ, SwapYZ, Torus, Translate, Union, WorldRegion, Wrap }; pub use units::{Coord, Meters, Index}; pub use vec::{Vec2, Vec3}; diff --git a/src/geom/region/mod.rs b/src/geom/region/mod.rs index a75b30d..0f1794f 100644 --- a/src/geom/region/mod.rs +++ b/src/geom/region/mod.rs @@ -1,7 +1,8 @@ -use crate::geom::Meters; +use crate::geom::{Coord, Index, Meters}; use dyn_clone::{self, DynClone}; use serde::{Serialize, Deserialize}; +use std::collections::{BTreeMap, BTreeSet}; mod primitives; pub use primitives::*; @@ -25,6 +26,61 @@ pub fn union(r1: T1, r2: T2) -> Unio Union::new().with(r1).with(r2) } +/// returns true if there's a path (via the cardinal directions) from p0 to p1 within this region. +pub fn is_connected(r: &R, p0: C, p1: C, feat_size: f32) -> bool { + let mut pts = BTreeSet::new(); + pts = crawl_to(pts, r, p0.to_index(feat_size), p1.to_index(feat_size), feat_size); + pts.contains(&p1.to_index(feat_size)) +} + +fn crawl_to(mut pts: BTreeSet, r: &R, p0: Index, p1: Index, feat_size: f32) -> BTreeSet { + if pts.contains(&p1) || pts.contains(&p0) { + // either already reached the goal, or already crawled this location + return pts; + } + if r.contains(p0.to_meters(feat_size)) { + pts.insert(p0); + pts = crawl_to(pts, r, p0.left(), p1, feat_size); + pts = crawl_to(pts, r, p0.right(), p1, feat_size); + pts = crawl_to(pts, r, p0.up(), p1, feat_size); + pts = crawl_to(pts, r, p0.down(), p1, feat_size); + pts = crawl_to(pts, r, p0.out(), p1, feat_size); + pts = crawl_to(pts, r, p0.in_(), p1, feat_size); + } + pts +} + +/// returns the minimal distance to crawl from p0 to p1 (via movements along a cardinal direction) +pub fn distance_to(r: &R, p0: C, p1: C, feat_size: f32) -> Option { + let mut pts = BTreeMap::new(); + crawl_with_distance(&mut pts, r, p0.to_index(feat_size), 0, feat_size); + println!("pts size: {}", pts.len()); + pts.get(&p1.to_index(feat_size)).map(|d| *d as f32 * feat_size) +} + +fn crawl_with_distance(pts: &mut BTreeMap, r: &R, current: Index, current_value: u32, feat_size: f32) { + use std::collections::btree_map::Entry; + if !r.contains(current.to_meters(feat_size)) { + return; + } + + match pts.entry(current) { + Entry::Occupied(old_value) if *old_value.get() <= current_value => { + return; // already crawled + } + other => { + *other.or_default() = current_value; + } + } + // crawl neighbors + crawl_with_distance(pts, r, current.left(), current_value+1, feat_size); + crawl_with_distance(pts, r, current.right(), current_value+1, feat_size); + crawl_with_distance(pts, r, current.up(), current_value+1, feat_size); + crawl_with_distance(pts, r, current.down(), current_value+1, feat_size); + crawl_with_distance(pts, r, current.out(), current_value+1, feat_size); + crawl_with_distance(pts, r, current.in_(), current_value+1, feat_size); +} + /// Region describing the entire simulation space #[derive(Copy, Clone, Serialize, Deserialize)] pub struct WorldRegion; @@ -200,6 +256,38 @@ impl Region for Wrap { } } +#[derive(Clone, Serialize, Deserialize)] +pub struct Dilate { + inner: Box, + rad: f32, +} + +impl Dilate { + pub fn new(inner: T, rad: f32) -> Self { + Self { inner: Box::new(inner), rad } + } + pub fn new_iterated(inner: T, resolution: f32, iters: usize) -> Self { + let mut it = Self::new(inner, resolution); + for _ in 1..iters { + it = Self::new(it, resolution); + } + it + } +} + +#[typetag::serde] +impl Region for Dilate { + fn contains(&self, p: Meters) -> bool { + self.inner.contains(p) + || self.inner.contains(p + Meters::new(self.rad, 0.0, 0.0)) + || self.inner.contains(p + Meters::new(0.0, -self.rad, 0.0)) + || self.inner.contains(p + Meters::new(0.0, self.rad, 0.0)) + || self.inner.contains(p + Meters::new(0.0, -self.rad, 0.0)) + || self.inner.contains(p + Meters::new(0.0, 0.0, self.rad)) + || self.inner.contains(p + Meters::new(0.0, 0.0, -self.rad)) + } +} + #[cfg(test)] mod test { use super::*; diff --git a/src/geom/region/primitives.rs b/src/geom/region/primitives.rs index 76f4de2..b925323 100644 --- a/src/geom/region/primitives.rs +++ b/src/geom/region/primitives.rs @@ -14,7 +14,7 @@ pub struct CylinderZ { } impl CylinderZ { - // TODO: should be unit-typed + // TODO: should be unit-typed (Meters) pub fn new(center: Vec2, radius: f32) -> Self { Self { center, @@ -127,27 +127,84 @@ impl Region for Sphere { #[derive(Copy, Clone, Default, Serialize, Deserialize)] pub struct Cube { - /// Lower corner (i.e. (x0, y0, z0)) - lower: Meters, - /// Upper corner (i.e. (x1, y1, z1)) where x1 >= x0, y1 >= y0, z1 >= z0 - upper: Meters, + /// (x0, y0, z0)) + small: Meters, + /// (i.e. (x1, y1, z1)) where x1 >= x0, y1 >= y0, z1 >= z0 + large: Meters, } impl Cube { - pub fn new(lower: Meters, upper: Meters) -> Self { - Self { lower, upper } + pub fn new(small: Meters, large: Meters) -> Self { + Self { small, large } } pub fn new_centered(center: Meters, size: Meters) -> Self { Self::new(center - size*0.5, center + size*0.5) } + /// 'new', but handle the case where pt1's coordinate is lower than pt0 by 'swapping' them + pub fn new_including_negatives(pt0: Meters, pt1: Meters) -> Self { + Self::new( + Meters::new(pt0.x().min(pt1.x()), pt0.y().min(pt1.y()), pt0.z().min(pt1.z())), + Meters::new(pt0.x().max(pt1.x()), pt0.y().max(pt1.y()), pt0.z().max(pt1.z())), + ) + } pub fn x_range(&self) -> Range { - self.lower.x()..self.upper.x() + self.small.x()..self.large.x() } pub fn y_range(&self) -> Range { - self.lower.y()..self.upper.y() + self.small.y()..self.large.y() } pub fn z_range(&self) -> Range { - self.lower.z()..self.upper.z() + self.small.z()..self.large.z() + } + pub fn small(&self) -> Meters { + self.small + } + pub fn large(&self) -> Meters { + self.large + } + + pub fn center(&self) -> Meters { + (self.small + self.large) * 0.5 + } + + pub fn bot_left_out(&self) -> Meters { + Meters::new(self.small.x(), self.large.y(), self.small.z()) + } + pub fn bot_left_center(&self) -> Meters { + Meters::new(self.small.x(), self.large.y(), self.center().z()) + } + pub fn bot_left_in(&self) -> Meters { + Meters::new(self.small.x(), self.large.y(), self.large.z()) + } + + pub fn bot_right_out(&self) -> Meters { + Meters::new(self.large.x(), self.large.y(), self.small.z()) + } + pub fn bot_right_center(&self) -> Meters { + Meters::new(self.large.x(), self.large.y(), self.center().z()) + } + pub fn bot_right_in(&self) -> Meters { + Meters::new(self.large.x(), self.large.y(), self.large.z()) + } + + pub fn top_left_out(&self) -> Meters { + Meters::new(self.small.x(), self.small.y(), self.small.z()) + } + pub fn top_left_center(&self) -> Meters { + Meters::new(self.small.x(), self.small.y(), self.center().z()) + } + pub fn top_left_in(&self) -> Meters { + Meters::new(self.small.x(), self.small.y(), self.large.z()) + } + + pub fn top_right_out(&self) -> Meters { + Meters::new(self.large.x(), self.small.y(), self.small.z()) + } + pub fn top_right_center(&self) -> Meters { + Meters::new(self.large.x(), self.small.y(), self.center().z()) + } + pub fn top_right_in(&self) -> Meters { + Meters::new(self.large.x(), self.small.y(), self.large.z()) } } diff --git a/src/geom/units.rs b/src/geom/units.rs index 652c61e..6a49748 100644 --- a/src/geom/units.rs +++ b/src/geom/units.rs @@ -6,7 +6,10 @@ use std::ops::{Add, Deref, Div, Mul, Neg, Sub}; pub trait Coord: Copy + Clone { fn to_meters(&self, feature_size: f32) -> Meters; + /// rounds fn to_index(&self, feature_size: f32) -> Index; + fn to_index_ceil(&self, feature_size: f32) -> Index; + fn to_index_floor(&self, feature_size: f32) -> Index; fn from_meters(other: Meters, feature_size: f32) -> Self; fn from_index(other: Index, feature_size: f32) -> Self; fn from_either(i: Index, m: Meters) -> Self; @@ -19,6 +22,15 @@ impl Meters { pub fn new(x: R, y: R, z: R) -> Self { Self(Vec3::new(x.to_f32(), y.to_f32(), z.to_f32())) } + pub fn new_x(x: R) -> Self { + Self::new(x.to_f32(), 0.0, 0.0) + } + pub fn new_y(y: R) -> Self { + Self::new(0.0, y.to_f32(), 0.0) + } + pub fn new_z(z: R) -> Self { + Self::new(0.0, 0.0, z.to_f32()) + } } impl Coord for Meters { @@ -28,6 +40,12 @@ impl Coord for Meters { fn to_index(&self, feature_size: f32) -> Index { Index((self.0 / feature_size).round().into()) } + fn to_index_ceil(&self, feature_size: f32) -> Index { + Index((self.0 / feature_size).ceil().into()) + } + fn to_index_floor(&self, feature_size: f32) -> Index { + Index((self.0 / feature_size).floor().into()) + } fn from_meters(other: Meters, _feature_size: f32) -> Self { other } @@ -106,6 +124,25 @@ impl Index { pub fn volume(&self) -> usize { (self.0.x() as usize) * (self.0.y() as usize) * (self.0.z() as usize) } + + pub fn left(&self) -> Self { + Self::new(self.x() - 1, self.y(), self.z()) + } + pub fn right(&self) -> Self { + Self::new(self.x() + 1, self.y(), self.z()) + } + pub fn up(&self) -> Self { + Self::new(self.x(), self.y() - 1, self.z()) + } + pub fn down(&self) -> Self { + Self::new(self.x(), self.y() + 1, self.z()) + } + pub fn out(&self) -> Self { + Self::new(self.x(), self.y(), self.z() - 1) + } + pub fn in_(&self) -> Self { + Self::new(self.x(), self.y(), self.z() + 1) + } } impl Coord for Index { @@ -115,6 +152,12 @@ impl Coord for Index { fn to_index(&self, _feature_size: f32) -> Index { *self } + fn to_index_ceil(&self, _feature_size: f32) -> Index { + *self + } + fn to_index_floor(&self, _feature_size: f32) -> Index { + *self + } fn from_meters(other: Meters, feature_size: f32) -> Self { other.to_index(feature_size) } diff --git a/src/geom/vec.rs b/src/geom/vec.rs index 838e38e..b18cfef 100644 --- a/src/geom/vec.rs +++ b/src/geom/vec.rs @@ -327,6 +327,12 @@ impl Vec3 { pub fn round(&self) -> Self { Self::new(self.x().round(), self.y().round(), self.z().round()) } + pub fn ceil(&self) -> Self { + Self::new(self.x().ceil(), self.y().ceil(), self.z().ceil()) + } + pub fn floor(&self) -> Self { + Self::new(self.x().floor(), self.y().floor(), self.z().floor()) + } } impl Into<(R, R, R)> for Vec3 { diff --git a/src/render.rs b/src/render.rs index 42465b1..dc3c167 100644 --- a/src/render.rs +++ b/src/render.rs @@ -494,9 +494,18 @@ impl Renderer for Y4MRenderer { struct MultiRendererElement { step_frequency: u64, + step_limit: Option, renderer: Box>, } +impl MultiRendererElement { + fn work_this_frame(&self, frame: u64) -> bool { + frame % self.step_frequency == 0 && match self.step_limit { + None => true, + Some(end) => frame < end, + } + } +} pub struct MultiRenderer { renderers: RwLock>>, @@ -514,18 +523,19 @@ impl MultiRenderer { pub fn new() -> Self { Default::default() } - pub fn push + 'static>(&self, renderer: R, step_frequency: u64) { + pub fn push + 'static>(&self, renderer: R, step_frequency: u64, step_limit: Option) { self.renderers.write().unwrap().push(MultiRendererElement { step_frequency, + step_limit, renderer: Box::new(renderer), }); } - pub fn with + 'static>(self, renderer: R, step_frequency: u64) -> Self { - self.push(renderer, step_frequency); + pub fn with + 'static>(self, renderer: R, step_frequency: u64, step_limit: Option) -> Self { + self.push(renderer, step_frequency, step_limit); self } pub fn any_work_for_frame(&self, frame: u64) -> bool { - self.renderers.read().unwrap().iter().any(|m| frame % m.step_frequency == 0) + self.renderers.read().unwrap().iter().any(|m| m.work_this_frame(frame)) } } @@ -541,7 +551,7 @@ impl Renderer for MultiRenderer { fn render_with_image(&self, state: &S, im: &RgbImage, measurements: &[Box], config: RenderConfig) { for r in &*self.renderers.read().unwrap() { - if state.step_no() % r.step_frequency == 0 { + if r.work_this_frame(state.step_no()) { r.renderer.render_with_image(state, im, measurements, config); } } diff --git a/src/stim.rs b/src/stim.rs index 34cf035..e8cd71c 100644 --- a/src/stim.rs +++ b/src/stim.rs @@ -153,6 +153,12 @@ pub trait TimeVarying3: Sized { } } +impl TimeVarying1 for f32 { + fn at(&self, _t_sec: f32) -> f32 { + *self + } +} + #[derive(Clone)] pub struct Sinusoid { amp: A,