fdtd-coremem/src/driver.rs

302 lines
11 KiB
Rust
Raw Normal View History

use crate::geom::{Coord, Meters, Region, Vec3};
use crate::mat::{self, Pml};
use crate::meas::{self, AbstractMeasurement};
use crate::real::Real;
use crate::render::{self, MultiRenderer, Renderer};
use crate::sim::{GenericSim, MaterialSim, SampleableSim, SimState};
use crate::sim::spirv::SpirvSim;
2020-09-26 22:50:34 +00:00
use crate::stim::AbstractStimulus;
2020-12-08 06:55:24 +00:00
use log::{info, trace};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
use std::time::{Duration, Instant};
use threadpool::ThreadPool;
pub struct Driver<S=SimState> {
pub state: S,
renderer: Arc<MultiRenderer<S>>,
// TODO: use Rayon's thread pool?
render_pool: ThreadPool,
render_channel: (SyncSender<()>, Receiver<()>),
time_spent_stepping: Duration,
2020-09-26 22:50:34 +00:00
time_spent_on_stimuli: Duration,
2020-12-18 06:22:25 +00:00
time_spent_prepping_render: Duration,
time_spent_blocked_on_render: Duration,
time_spent_rendering: Arc<Mutex<Duration>>,
measurements: Vec<Box<dyn AbstractMeasurement>>,
stimuli: StimuliAdapter,
start_time: Instant,
last_diag_time: Instant,
}
impl<R: Real, M: Default> Driver<SimState<R, M>> {
2021-06-08 01:25:11 +00:00
pub fn new<C: Coord>(size: C, feature_size: f32) -> Self {
Self::new_with_state(SimState::new(size.to_index(feature_size), feature_size))
}
}
impl Driver<SpirvSim> {
pub fn new_spirv<C: Coord>(size: C, feature_size: f32) -> Self {
2021-08-09 06:15:37 +00:00
Self::new_with_state(SpirvSim::new(size.to_index(feature_size), feature_size))
}
}
impl<S> Driver<S> {
pub fn new_with_state(state: S) -> Self {
Self {
state,
renderer: Arc::new(MultiRenderer::new()),
render_pool: ThreadPool::new(3),
render_channel: sync_channel(0),
time_spent_stepping: Default::default(),
2020-09-26 22:50:34 +00:00
time_spent_on_stimuli: Default::default(),
2020-12-18 06:22:25 +00:00
time_spent_prepping_render: Default::default(),
time_spent_blocked_on_render: Default::default(),
time_spent_rendering: Default::default(),
2020-12-18 04:07:25 +00:00
measurements: vec![
Box::new(meas::Time),
Box::new(meas::Meta),
Box::new(meas::Energy::world()),
Box::new(meas::Power::world()),
2020-12-18 04:07:25 +00:00
],
stimuli: StimuliAdapter::new(),
start_time: Instant::now(),
last_diag_time: Instant::now(),
}
}
}
impl<S> Driver<S> {
pub fn add_stimulus<Stim: AbstractStimulus + 'static>(&mut self, s: Stim) {
2020-09-26 22:50:34 +00:00
self.stimuli.push(Box::new(s))
}
pub fn add_measurement<Meas: AbstractMeasurement + 'static>(&mut self, m: Meas) {
self.measurements.push(Box::new(m));
}
pub fn set_steps_per_stim(&mut self, steps_per_stim: u64) {
self.stimuli.frame_interval = steps_per_stim;
}
}
impl<S: MaterialSim> Driver<S> {
pub fn fill_region<Reg: Region, M: Into<S::Material> + Clone>(&mut self, region: &Reg, mat: M) {
2021-01-03 05:34:35 +00:00
self.state.fill_region(region, mat);
}
}
impl<S: SampleableSim + Send + Sync + 'static> Driver<S> {
pub fn dyn_state(&mut self) -> &mut dyn SampleableSim {
&mut self.state
}
fn add_renderer<Rend: Renderer<S> + 'static>(
&mut self, renderer: Rend, name: &str, step_frequency: u64
) {
info!("render to {} at f={}", name, step_frequency);
self.renderer.push(renderer, step_frequency);
}
pub fn add_y4m_renderer<P: Into<PathBuf>>(&mut self, output: P, step_frequency: u64) {
let output = output.into();
let name = output.to_string_lossy().into_owned();
self.add_renderer(render::Y4MRenderer::new(output), &*name, step_frequency);
}
pub fn add_term_renderer(&mut self, step_frequency: u64) {
self.add_renderer(render::ColorTermRenderer, "terminal", step_frequency);
}
2021-06-15 07:47:59 +00:00
pub fn add_csv_renderer(&mut self, path: &str, step_frequency: u64) {
self.add_renderer(render::CsvRenderer::new(path), path, step_frequency);
}
}
impl<S: SampleableSim + Send + Sync + Serialize + 'static> Driver<S> {
pub fn add_serializer_renderer(&mut self, out_base: &str, step_frequency: u64) {
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);
}
}
impl<S: SampleableSim + Send + Sync + Serialize + for<'a> Deserialize<'a> + 'static> Driver<S> {
pub fn add_state_file(&mut self, state_file: &str, snapshot_frequency: u64) {
let ser = render::SerializerRenderer::new(state_file);
if let Some(state) = ser.try_load() {
self.state = state.state;
}
self.add_renderer(ser, state_file, snapshot_frequency);
}
}
impl<S: GenericSim + Clone + Default + Send + Sync + 'static> Driver<S> {
fn render(&mut self) {
2020-12-18 06:22:25 +00:00
let prep_start = Instant::now();
let their_state = self.state.clone();
let their_measurements = self.measurements.clone();
let renderer = self.renderer.clone();
let time_spent_rendering = self.time_spent_rendering.clone();
let sender = self.render_channel.0.clone();
self.render_pool.execute(move || {
// unblock the main thread (this limits the number of renders in-flight at any time
sender.send(()).unwrap();
trace!("render begin");
let start_time = Instant::now();
renderer.render(&their_state, &*their_measurements, Default::default());
*time_spent_rendering.lock().unwrap() += start_time.elapsed();
trace!("render end");
});
2020-12-18 06:22:25 +00:00
self.time_spent_prepping_render += prep_start.elapsed();
let block_start = Instant::now();
self.render_channel.1.recv().unwrap();
self.time_spent_blocked_on_render += block_start.elapsed();
}
/// Return the number of steps actually stepped
fn step_at_most(&mut self, at_most: u32) -> u32 {
assert!(at_most != 0);
let start_step = self.state.step_no();
if self.stimuli.should_apply(start_step) {
self.stimuli.real_time = self.state.time();
self.stimuli.time_step = self.state.timestep();
info!("updating stimuli");
}
if self.renderer.any_work_for_frame(start_step) {
self.render();
}
let mut can_step = at_most;
while can_step < at_most && !self.renderer.any_work_for_frame(start_step + can_step as u64) {
can_step += 1;
}
trace!("step begin");
let start_time = Instant::now();
self.state.step_multiple(can_step, &self.stimuli);
self.time_spent_stepping += start_time.elapsed();
trace!("step end");
if self.last_diag_time.elapsed().as_secs_f64() >= 5.0 {
self.last_diag_time = Instant::now();
let step = self.state.step_no();
let step_time = self.time_spent_stepping.as_secs_f64();
let stim_time = self.time_spent_on_stimuli.as_secs_f64();
let render_time = self.time_spent_rendering.lock().unwrap().as_secs_f64();
2020-12-18 06:22:25 +00:00
let render_prep_time = self.time_spent_prepping_render.as_secs_f64();
let block_time = self.time_spent_blocked_on_render.as_secs_f64();
let overall_time = self.start_time.elapsed().as_secs_f64();
let fps = (self.state.step_no() as f64) / overall_time;
let sim_time = self.state.time() as f64;
info!(
2020-12-18 06:22:25 +00:00
"t={:.2e} frame {:06} fps: {:6.2} (sim: {:.1}s, stim: {:.1}s, [render: {:.1}s], blocked: {:.1}s, render_prep: {:.1}s, other: {:.1}s)",
sim_time,
step,
fps,
step_time,
stim_time,
render_time,
block_time,
2020-12-18 06:22:25 +00:00
render_prep_time,
overall_time - step_time - stim_time - block_time - render_prep_time,
);
}
can_step as u32
}
pub fn step_multiple(&mut self, num_steps: u32) {
let mut steps_remaining = num_steps;
while steps_remaining != 0 {
steps_remaining -= self.step_at_most(steps_remaining);
}
}
pub fn step(&mut self) {
self.step_multiple(1);
}
2021-06-08 01:25:11 +00:00
pub fn step_until(&mut self, deadline: f32) {
while self.dyn_state().time() < deadline {
self.step_multiple(100);
}
// render the final frame
self.render();
self.render_pool.join();
}
}
impl<S: MaterialSim> Driver<S> {
pub fn add_pml_boundary<C: Coord, R: Real>(&mut self, thickness: C)
where S::Material: From<Pml<R>>
{
2021-05-31 03:20:27 +00:00
let timestep = self.state.timestep();
self.state.fill_boundary_using(thickness, |boundary_ness| {
2021-06-08 01:25:11 +00:00
let b = boundary_ness.elem_pow(3.0);
let conductivity = b * (0.5 / timestep);
Pml::new(conductivity)
2021-05-31 03:20:27 +00:00
});
}
2021-05-31 03:20:27 +00:00
pub fn add_classical_boundary<C: Coord, R: Real>(&mut self, thickness: C)
where S::Material: From<mat::Conductor<R>>
{
let timestep = self.state.timestep();
self.state.fill_boundary_using(thickness, |boundary_ness| {
2021-06-08 01:25:11 +00:00
let b = boundary_ness.elem_pow(3.0);
let cond = b * (0.5 / timestep);
let iso_cond = cond.x() + cond.y() + cond.z();
2021-06-09 22:46:45 +00:00
let iso_conductor = mat::db::conductor(iso_cond);
iso_conductor
});
}
}
/// Adapts the stimuli to be applied only every so often, to improve perf
struct StimuliAdapter {
stim: Vec<Box<dyn AbstractStimulus>>,
/// How many frames to go between applications of the stimulus.
frame_interval: u64,
real_time: f32,
time_step: f32,
}
impl AbstractStimulus for StimuliAdapter {
fn at(&self, t_sec: f32, pos: Meters) -> Vec3<f32> {
// TODO: remove this stuff (here only for testing)
if true {
// interpolation unaware (i.e. let the Sim backend do it)
self.stim.at(t_sec, pos)
} else if false {
// delta-fn "interpolation"
self.stim.at(t_sec, pos) * (self.frame_interval as f32)
} else if false {
// step-fn "interpolation"
self.stim.at(self.real_time, pos)
} else {
// linear interpolation
let interp_width = self.frame_interval as f32 * self.time_step;
let prev = self.stim.at(self.real_time, pos);
let next = self.stim.at(self.real_time + interp_width, pos);
let interp = (t_sec - self.real_time) / interp_width;
prev * (1.0 - interp) + next * interp
}
}
}
impl StimuliAdapter {
fn new() -> Self {
Self {
stim: Default::default(),
frame_interval: 1,
real_time: 0.0,
time_step: 0.0,
}
}
fn should_apply(&self, frame: u64) -> bool {
(frame % self.frame_interval == 0) && self.stim.len() != 0
}
fn push(&mut self, s: Box<dyn AbstractStimulus>) {
self.stim.push(s)
}
}