fdtd-coremem/crates/coremem/src/driver.rs

293 lines
10 KiB
Rust
Raw Normal View History

2022-08-12 05:31:05 +00:00
use crate::diagnostics::SyncDiagnostics;
use crate::geom::{Coord, Index, Region};
use crate::mat;
use crate::meas::{self, AbstractMeasurement};
2022-07-27 23:34:50 +00:00
use crate::real::Real;
use crate::render::{self, MultiRenderer, Renderer};
2022-07-29 05:32:29 +00:00
use crate::sim::AbstractSim;
use crate::sim::units::{Frame, Time};
use crate::stim::{Stimulus, DynStimuli};
use coremem_cross::compound::list;
2020-12-08 06:55:24 +00:00
use log::{info, trace};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
2022-08-12 05:31:05 +00:00
use std::sync::Arc;
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
2022-08-12 05:31:05 +00:00
use std::time::Instant;
use threadpool::ThreadPool;
pub struct Driver<S, Stim=DynStimuli> {
state: S,
renderer: Arc<MultiRenderer<S>>,
// TODO: use Rayon's thread pool?
render_pool: ThreadPool,
render_channel: (SyncSender<()>, Receiver<()>),
measurements: Vec<Arc<dyn AbstractMeasurement<S>>>,
stimuli: Stim,
/// simulation end time
sim_end_time: Option<Frame>,
diag: SyncDiagnostics,
last_diag_time: Instant,
}
impl<S: AbstractSim, Stim> Driver<S, Stim> {
pub fn new_with_stim(mut state: S, stimuli: Stim) -> Self {
let diag = SyncDiagnostics::new();
state.use_diagnostics(diag.clone());
Self {
state,
renderer: Arc::new(MultiRenderer::new()),
render_pool: ThreadPool::new(3),
render_channel: sync_channel(0),
2020-12-18 04:07:25 +00:00
measurements: vec![
Arc::new(meas::Time),
Arc::new(meas::Meta),
Arc::new(meas::Energy::world()),
Arc::new(meas::Power::world()),
2020-12-18 04:07:25 +00:00
],
stimuli,
2022-01-03 20:48:51 +00:00
sim_end_time: None,
diag,
last_diag_time: Instant::now(),
}
}
pub fn add_measurement<Meas: AbstractMeasurement<S> + 'static>(&mut self, m: Meas) {
self.measurements.push(Arc::new(m));
}
}
impl<S: AbstractSim> Driver<S, DynStimuli> {
pub fn new(state: S) -> Self {
Self::new_with_stim(state, DynStimuli::default())
}
pub fn add_stimulus<Stim: Stimulus + 'static>(&mut self, s: Stim) {
self.stimuli.push(Box::new(s))
}
}
impl<S: AbstractSim> Driver<S, list::Empty> {
pub fn new_unboxed_stim(state: S) -> Self {
Self::new_with_stim(state, list::Empty::default())
}
}
impl<S: AbstractSim, Stim> Driver<S, Stim> {
pub fn with_add_stimulus<E>(self, s: E) -> Driver<S, list::Prepended<E, Stim>>
where Stim: list::Prependable
{
Driver {
state: self.state,
renderer: self.renderer,
render_pool: self.render_pool,
render_channel: self.render_channel,
measurements: self.measurements,
stimuli: self.stimuli.prepend(s),
sim_end_time: self.sim_end_time,
diag: self.diag,
last_diag_time: self.last_diag_time,
}
}
pub fn with_stimulus<NewStim>(self, stimuli: NewStim) -> Driver<S, NewStim> {
Driver {
state: self.state,
renderer: self.renderer,
render_pool: self.render_pool,
render_channel: self.render_channel,
measurements: self.measurements,
stimuli,
sim_end_time: self.sim_end_time,
diag: self.diag,
last_diag_time: self.last_diag_time,
}
}
}
impl<S: AbstractSim, Stim> Driver<S, Stim> {
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);
}
2022-07-29 05:31:47 +00:00
pub fn test_region_filled<Reg: Region, M>(&mut self, region: &Reg, mat: M) -> bool
where
M: Into<S::Material> + Clone,
S::Material: PartialEq
{
self.state.test_region_filled(region, mat)
}
pub fn size(&self) -> Index {
self.state.size()
}
pub fn timestep(&self) -> f32 {
self.state.timestep()
}
pub fn time(&self) -> f32 {
self.state.time()
}
pub fn add_classical_boundary<C: Coord>(&mut self, thickness: C)
where S::Material: From<mat::IsomorphicConductor<f32>>
{
self.add_classical_boundary_explicit::<f32, _>(thickness)
}
/// the CPU code is parameterized over `Real`: you'll need to use this interface to get access
/// to that, if using a CPU driver. otherwise, use `add_classical_boundary`
pub fn add_classical_boundary_explicit<R: Real, C: Coord>(&mut self, thickness: C)
where S::Material: From<mat::IsomorphicConductor<R>>
{
let timestep = self.state.timestep();
self.state.fill_boundary_using(thickness, |boundary_ness| {
let b = boundary_ness.elem_pow(3.0);
let cond = b * (0.5 / timestep);
let iso_cond = cond.x() + cond.y() + cond.z();
let iso_conductor = mat::IsomorphicConductor::new(iso_cond.cast());
iso_conductor
});
}
}
impl<S: AbstractSim + 'static, Stim> Driver<S, Stim> {
fn add_renderer<Rend: Renderer<S> + 'static>(
&mut self, renderer: Rend, name: &str, step_frequency: u64, frame_limit: Option<u64>
) {
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<P: Into<PathBuf>>(&mut self, output: P, step_frequency: u64, frame_limit: Option<u64>) {
let output = output.into();
let name = output.to_string_lossy().into_owned();
self.add_renderer(render::Y4MRenderer::new(output), &*name, step_frequency, frame_limit);
}
pub fn add_term_renderer(&mut self, step_frequency: u64, frame_limit: Option<u64>) {
self.add_renderer(render::ColorTermRenderer, "terminal", step_frequency, frame_limit);
}
2021-06-15 07:47:59 +00:00
pub fn add_csv_renderer(&mut self, path: &str, step_frequency: u64, frame_limit: Option<u64>) {
self.add_renderer(render::CsvRenderer::new(path), path, step_frequency, frame_limit);
2021-06-15 07:47:59 +00:00
}
}
impl<S: AbstractSim + Serialize + 'static, Stim> Driver<S, Stim> {
pub fn add_serializer_renderer(&mut self, out_base: &str, step_frequency: u64, frame_limit: Option<u64>) {
let fmt_str = format!("{out_base}{{step_no}}.bc", out_base=out_base);
self.add_renderer(render::SerializerRenderer::new_generic(&*fmt_str), &*fmt_str, step_frequency, frame_limit);
}
}
impl<S, Stim> Driver<S, Stim>
where
S: AbstractSim + Send + Sync + Serialize + for<'a> Deserialize<'a> + 'static
{
/// instruct the driver to periodically save the simulation state to the provided path.
/// also attempts to load an existing state file, returning `true` on success.
pub fn add_state_file(&mut self, state_file: &str, snapshot_frequency: u64) -> bool {
let ser = render::SerializerRenderer::new(state_file);
let loaded = ser.try_load().map(|s| {
self.state = s.state;
self.state.use_diagnostics(self.diag.clone());
}).is_some();
self.add_renderer(ser, state_file, snapshot_frequency, None);
loaded
}
}
impl<S, Stim> Driver<S, Stim>
where
S: AbstractSim + Clone + Default + Send + 'static,
Stim: Stimulus,
{
fn render(&mut self) {
self.diag.instrument_render_prep(|| {
let diag_handle = self.diag.clone();
let their_state = self.state.clone();
let their_measurements = self.measurements.clone();
let renderer = self.renderer.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");
diag_handle.instrument_render_cpu_side(|| {
let meas: Vec<&dyn AbstractMeasurement<S>> = their_measurements.iter().map(|m| &**m).collect();
renderer.render(&their_state, &*meas, Default::default());
});
trace!("render end");
});
});
self.diag.instrument_render_blocked(|| {
self.render_channel.1.recv().unwrap();
});
}
/// 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.renderer.any_work_for_frame(start_step) {
self.render();
}
let mut can_step = 1;
while can_step < at_most && !self.renderer.any_work_for_frame(start_step + can_step as u64) {
can_step += 1;
}
trace!("step begin");
self.diag.instrument_step(can_step as u64, || {
self.state.step_multiple(can_step, &self.stimuli);
});
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 diagstr = self.diag.format();
let sim_time = self.state.time() as f64;
2022-01-03 20:48:51 +00:00
let percent_complete = match self.sim_end_time {
Some(t) => format!("[{:.1}%] ", 100.0 * self.state.time() / *t.to_seconds(self.timestep())),
2022-01-03 20:48:51 +00:00
None => "".to_owned(),
};
info!(
"{}t={:.2e} frame {:06} {}",
percent_complete, sim_time, step, diagstr
);
}
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);
}
/// Returns the number of timesteps needed to reach the end time
pub fn steps_until<T: Time>(&mut self, sim_end_time: T) -> u64 {
let sim_end_step = sim_end_time.to_frame(self.state.timestep());
let start_step = self.state.step_no();
sim_end_step.saturating_sub(start_step)
}
pub fn step_until<T: Time>(&mut self, sim_end_time: T) {
let sim_end_time = sim_end_time.to_frame(self.state.timestep());
2022-01-03 20:48:51 +00:00
self.sim_end_time = Some(sim_end_time);
let mut stepped = false;
2022-07-29 02:19:57 +00:00
while self.state.step_no() < *sim_end_time {
self.step_multiple(100);
stepped = true;
}
if stepped {
// render the final frame -- unless we already *have*
self.render();
}
self.render_pool.join();
2022-01-03 20:48:51 +00:00
self.sim_end_time = None;
}
}