AKA I made these changes a month ago but forgot to commit them and don't
really want to figure out what they did.
This commit is contained in:
2020-11-27 21:22:42 -08:00
parent 1a9093315a
commit 9d15e126a7
6 changed files with 228 additions and 86 deletions

View File

@@ -9,6 +9,7 @@ edition = "2018"
[dependencies]
ansi_term = "0.12"
decorum = "0.3"
dyn-clone = "1.0"
enum_dispatch = "0.3"
env_logger = "0.7"
font8x8 = "0.2"
@@ -18,7 +19,8 @@ lazy_static = "1.4"
log = "0.4"
ndarray = { version = "0.13", features = ["rayon"] }
piecewise-linear = "0.1"
plotly = { version = "0.6", features = ["kaleido", "plotly_ndarray"] }
plotly = { version = "0.6", features = ["kaleido", "plotly_ndarray"], path = "../plotly/plotly" }
threadpool = "1.8"
y4m = "0.7"
[dev-dependencies]

View File

@@ -31,23 +31,31 @@ fn main() {
let depth_px = from_m(depth);
let size_px = Index((width_px, width_px, depth_px).into());
let mut driver = Driver::new(size_px, feat_size);
// driver.set_steps_per_frame(8);
//driver.set_steps_per_frame(8);
//driver.set_steps_per_frame(20);
//driver.set_steps_per_frame(40);
//driver.set_steps_per_frame(80);
driver.set_steps_per_frame(120);
//driver.set_steps_per_frame(120);
driver.set_steps_per_frame(160);
//driver.set_steps_per_frame(200);
// driver.add_y4m_renderer(&*format!("toroid25d.5-flt{}-{}-feat{}um-{:.1e}A-{:.1e}s--radii{}um-{}um-{}um-{}um.y4m",
// std::mem::size_of::<Flt>() * 8,
// *size_px,
// m_to_um(feat_size),
// peak_current,
// current_duration,
// m_to_um(conductor_outer_rad),
// m_to_um(ferro_inner_rad),
// m_to_um(ferro_outer_rad),
// m_to_um(ferro_depth),
// ));
driver.add_plotly_renderer();
let base = "toroid25d-7";
let _ = std::fs::create_dir(base);
let prefix = format!("{}/{}-flt{}-{}-feat{}um-{}mA-{}ps--radii{}um-{}um-{}um-{}um",
base,
base,
std::mem::size_of::<Flt>() * 8,
*size_px,
m_to_um(feat_size),
(peak_current * 1e3) as i64,
(current_duration * 1e12) as i64,
m_to_um(conductor_outer_rad),
m_to_um(ferro_inner_rad),
m_to_um(ferro_outer_rad),
m_to_um(ferro_depth),
);
let _ = std::fs::create_dir(&prefix);
driver.add_y4m_renderer(&*format!("{}.y4m", prefix));
driver.add_plotly_renderer(&*format!("{}/frame-", prefix));
let conductor_region = CylinderZ::new(
Vec2::new(half_width, half_width),
conductor_outer_rad);

View File

@@ -8,32 +8,43 @@ use crate::stim::AbstractStimulus;
use log::{info, debug, trace};
use std::path::PathBuf;
use std::time::{Duration, SystemTime};
use std::sync::{Arc, Mutex};
use std::sync::mpsc::{sync_channel, SyncSender, Receiver};
use std::time::{Duration, Instant};
use threadpool::ThreadPool;
pub struct Driver {
pub state: SimState,
renderer: MultiRenderer,
renderer: Arc<MultiRenderer>,
render_pool: ThreadPool,
render_channel: (SyncSender<()>, Receiver<()>),
steps_per_frame: u64,
time_spent_stepping: Duration,
time_spent_on_stimuli: Duration,
time_spent_rendering: Duration,
time_spent_blocked_on_render: Duration,
time_spent_rendering: Arc<Mutex<Duration>>,
measurements: Vec<Box<dyn AbstractMeasurement>>,
stimuli: Vec<Box<dyn AbstractStimulus>>,
start_time: SystemTime,
start_time: Instant,
last_diag_time: Instant,
}
impl Driver {
pub fn new<C: Coord>(size: C, feature_size: Flt) -> Self {
Driver {
state: SimState::new(size.to_index(feature_size), feature_size),
renderer: Default::default(),
renderer: Arc::new(MultiRenderer::new()),
render_pool: ThreadPool::new(3),
render_channel: sync_channel(0),
steps_per_frame: 1,
time_spent_stepping: Default::default(),
time_spent_on_stimuli: Default::default(),
time_spent_blocked_on_render: Default::default(),
time_spent_rendering: Default::default(),
measurements: vec![Box::new(meas::Time), Box::new(meas::Meta), Box::new(meas::Energy)],
stimuli: vec![],
start_time: SystemTime::now(),
start_time: Instant::now(),
last_diag_time: Instant::now(),
}
}
@@ -64,8 +75,8 @@ impl Driver {
self.add_renderer(render::Y4MRenderer::new(output), &*name);
}
pub fn add_plotly_renderer(&mut self) {
self.add_renderer(render::PlotlyRenderer, "plotly");
pub fn add_plotly_renderer(&mut self, out_base: &str) {
self.add_renderer(render::PlotlyRenderer::new(out_base), out_base);
}
pub fn add_term_renderer(&mut self) {
@@ -150,44 +161,62 @@ impl Driver {
}
}
fn render(&mut self) {
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 || {
sender.send(()).unwrap();
trace!("render begin");
let start_time = Instant::now();
renderer.render(&their_state, &*their_measurements);
*time_spent_rendering.lock().unwrap() += start_time.elapsed();
trace!("render end");
});
let block_start = Instant::now();
self.render_channel.1.recv().unwrap();
self.time_spent_blocked_on_render += block_start.elapsed();
}
pub fn step(&mut self) {
if self.state.step_no() % self.steps_per_frame == 0 {
trace!("render begin");
let start_time = SystemTime::now();
self.renderer.render(&self.state, &*self.measurements);
self.time_spent_rendering += start_time.elapsed().unwrap();
trace!("render end");
self.render();
}
{
trace!("stimuli begin");
let start_time = SystemTime::now();
let start_time = Instant::now();
for stim in &mut *self.stimuli {
stim.apply(&mut self.state);
}
self.time_spent_on_stimuli += start_time.elapsed().unwrap();
self.time_spent_on_stimuli += start_time.elapsed();
}
trace!("step begin");
let start_time = SystemTime::now();
let start_time = Instant::now();
self.state.step();
self.time_spent_stepping += start_time.elapsed().unwrap();
self.time_spent_stepping += start_time.elapsed();
trace!("step end");
let step = self.state.step_no();
if step % (10*self.steps_per_frame) == 0 {
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.as_secs_f64();
let overall_time = self.start_time.elapsed().unwrap().as_secs_f64();
let render_time = self.time_spent_rendering.lock().unwrap().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!(
"t={:.2e} frame {:06} fps: {:6.2} (sim: {:.1}s, stim: {:.1}s, render: {:.1}s, other: {:.1}s)",
"t={:.2e} frame {:06} fps: {:6.2} (sim: {:.1}s, stim: {:.1}s, render: {:.1}s, blocked: {:.1}s, other: {:.1}s)",
sim_time,
step,
fps,
step_time,
stim_time,
render_time,
block_time,
overall_time - step_time - stim_time - render_time
);
}

View File

@@ -2,13 +2,16 @@ use crate::flt::Flt;
use crate::geom::{Meters, Region};
use crate::mat::Material as _;
use crate::sim::{Cell, GenericSim};
use dyn_clone::{self, DynClone};
use std::fmt::Display;
use std::iter::Sum;
pub trait AbstractMeasurement {
pub trait AbstractMeasurement: Send + DynClone {
fn eval(&self, state: &dyn GenericSim) -> String;
}
dyn_clone::clone_trait_object!(AbstractMeasurement);
#[derive(Clone)]
pub struct Time;
impl AbstractMeasurement for Time {
@@ -17,6 +20,7 @@ impl AbstractMeasurement for Time {
}
}
#[derive(Clone)]
pub struct Meta;
impl AbstractMeasurement for Meta {
@@ -25,6 +29,7 @@ impl AbstractMeasurement for Meta {
}
}
#[derive(Clone)]
pub struct Label(pub String);
impl Label {
@@ -50,9 +55,10 @@ fn sum_over_region<T: Default + Sum<T>, R: Region, F: Fn(Meters, &Cell) -> T>(st
})
}
#[derive(Clone)]
pub struct Current<R>(pub R);
impl<R: Region + Display + Sync> AbstractMeasurement for Current<R> {
impl<R: Region + Clone + Display + Send + Sync> AbstractMeasurement for Current<R> {
fn eval(&self, state: &dyn GenericSim) -> String {
let current = sum_over_region(state, &self.0, |coord, _cell| state.current(coord));
format!("I({}): ({:.2e}, {:.2e}, {:.2e})", self.0, current.x(), current.y(), current.z())
@@ -65,6 +71,7 @@ fn loc(v: Meters) -> String {
}
/// M
#[derive(Clone)]
pub struct Magnetization(pub Meters);
impl AbstractMeasurement for Magnetization {
@@ -75,6 +82,7 @@ impl AbstractMeasurement for Magnetization {
}
/// B
#[derive(Clone)]
pub struct MagneticFlux(pub Meters);
impl AbstractMeasurement for MagneticFlux {
@@ -85,6 +93,7 @@ impl AbstractMeasurement for MagneticFlux {
}
/// H
#[derive(Clone)]
pub struct MagneticStrength(pub Meters);
impl AbstractMeasurement for MagneticStrength {
@@ -94,6 +103,7 @@ impl AbstractMeasurement for MagneticStrength {
}
}
#[derive(Clone)]
pub struct ElectricField(pub Meters);
impl AbstractMeasurement for ElectricField {
@@ -103,6 +113,7 @@ impl AbstractMeasurement for ElectricField {
}
}
#[derive(Clone)]
pub struct Energy;
impl AbstractMeasurement for Energy {

View File

@@ -6,12 +6,12 @@ use crate::sim::{Cell, GenericSim};
use crate::meas::AbstractMeasurement;
use font8x8::{BASIC_FONTS, GREEK_FONTS, UnicodeFonts as _};
use log::{trace, info};
use plotly::{Plot, ImageFormat};
use plotly::heat_map::HeatMap;
use plotly;
use image::{RgbImage, Rgb};
use imageproc::{pixelops, drawing};
use std::fs::File;
use std::path::PathBuf;
use std::sync::{Mutex, RwLock};
use y4m;
/// Accept a value from (-\inf, \inf) and return a value in (-1, 1).
@@ -54,6 +54,17 @@ fn scale_vector(x: Vec2, typical_mag: Flt) -> Vec2 {
x.with_mag(new_mag)
}
fn im_size(state: &dyn GenericSim, max_w: u32, max_h: u32) -> (u32, u32) {
let mut width = max_w;
let mut height = width * state.height() / state.width();
if height > max_h {
let stretch = max_h as f32 / height as f32;
width = (width as f32 * stretch) as _;
height = max_h;
}
(width, height)
}
struct RenderSteps<'a> {
im: RgbImage,
sim: &'a dyn GenericSim,
@@ -64,14 +75,7 @@ struct RenderSteps<'a> {
impl<'a> RenderSteps<'a> {
fn render(state: &'a dyn GenericSim, measurements: &'a [Box<dyn AbstractMeasurement>], z: u32) -> RgbImage {
let mut width = 640;
let max_height = 480;
let mut height = width * state.height() / state.width();
if height > max_height {
let stretch = max_height as f32 / height as f32;
width = (width as f32 * stretch) as _;
height = max_height;
}
let (width, height) = im_size(state, 640, 480);
trace!("rendering at {}x{} with z={}", width, height, z);
let mut me = Self::new(state, measurements, width, height, z);
me.render_scalar_field(10.0, false, 2, |cell| {
@@ -230,11 +234,13 @@ impl ImageRenderExt for RgbImage {
}
}
pub trait Renderer {
fn render(&mut self, state: &dyn GenericSim, measurements: &[Box<dyn AbstractMeasurement>]) {
pub trait Renderer: Send + Sync {
fn render(&self, state: &dyn GenericSim, measurements: &[Box<dyn AbstractMeasurement>]) {
self.render_with_image(state, &RenderSteps::render(state, measurements, state.depth() / 2));
}
fn render_with_image(&mut self, state: &dyn GenericSim, _im: &RgbImage) {
/// Not intended to be called directly by users; implement this if you want the image to be
/// computed using default settings and you just manage where to display/save it.
fn render_with_image(&self, state: &dyn GenericSim, _im: &RgbImage) {
self.render(state, &[]);
}
}
@@ -262,7 +268,7 @@ pub trait Renderer {
pub struct ColorTermRenderer;
impl Renderer for ColorTermRenderer {
fn render_with_image(&mut self, _state: &dyn GenericSim, im: &RgbImage) {
fn render_with_image(&self, _state: &dyn GenericSim, im: &RgbImage) {
let square = "";
let buf: String = im
.enumerate_rows()
@@ -279,27 +285,30 @@ impl Renderer for ColorTermRenderer {
pub struct Y4MRenderer {
out_path: PathBuf,
encoder: Option<y4m::Encoder<File>>,
encoder: Mutex<Option<y4m::Encoder<File>>>,
}
impl Y4MRenderer {
pub fn new<S: Into<PathBuf>>(output: S) -> Self {
Self {
out_path: output.into(),
encoder: None,
encoder: Mutex::new(None),
}
}
}
impl Renderer for Y4MRenderer {
fn render_with_image(&mut self, _state: &dyn GenericSim, im: &RgbImage) {
if self.encoder.is_none() {
let writer = File::create(&self.out_path).unwrap();
self.encoder = Some(y4m::encode(im.width() as usize, im.height() as usize, y4m::Ratio::new(30, 1))
.with_colorspace(y4m::Colorspace::C444)
.write_header(writer)
.unwrap()
);
fn render_with_image(&self, _state: &dyn GenericSim, im: &RgbImage) {
{
let mut enc = self.encoder.lock().unwrap();
if enc.is_none() {
let writer = File::create(&self.out_path).unwrap();
*enc = Some(y4m::encode(im.width() as usize, im.height() as usize, y4m::Ratio::new(30, 1))
.with_colorspace(y4m::Colorspace::C444)
.write_header(writer)
.unwrap()
);
}
}
let mut pix_y = Vec::new();
@@ -318,7 +327,8 @@ impl Renderer for Y4MRenderer {
}
let frame = y4m::Frame::new([&*pix_y, &*pix_u, &*pix_v], None);
let enc = self.encoder.as_mut().unwrap();
let mut lock = self.encoder.lock().unwrap();
let enc = lock.as_mut().unwrap();
trace!("write_frame begin");
let ret = enc.write_frame(&frame).unwrap();
trace!("write_frame end");
@@ -326,44 +336,112 @@ impl Renderer for Y4MRenderer {
}
}
pub struct PlotlyRenderer;
pub struct PlotlyRenderer {
out_base: String,
}
fn add_scatter(plot: &mut plotly::Plot, xv: &mut Vec<u32>, yv: &mut Vec<u32>, zv: &mut Vec<u32>, colors: &mut Vec<plotly::Rgba>) {
let xv = std::mem::replace(xv, Vec::new());
let yv = std::mem::replace(yv, Vec::new());
let zv = std::mem::replace(zv, Vec::new());
let colors = std::mem::replace(colors, Vec::new());
let scatter = plotly::Scatter::new3(xv, yv, zv)
.mode(plotly::common::Mode::Markers)
.marker(plotly::common::Marker::new()
.opacity(0.01)
//.size_array(sizes)
//.opacity_array(opacities)
.color_array(colors)
);
plot.add_trace(scatter);
}
impl PlotlyRenderer {
pub fn new(out_base: &str) -> Self {
Self {
out_base: out_base.into(),
}
}
}
impl Renderer for PlotlyRenderer {
fn render(&mut self, state: &dyn GenericSim, measurements: &[Box<dyn AbstractMeasurement>]) {
fn render(&self, state: &dyn GenericSim, measurements: &[Box<dyn AbstractMeasurement>]) {
use plotly::{ImageFormat, Plot, Rgba, Scatter};
use plotly::common::Marker;
use plotly::layout::{AspectMode, Axis, Layout, LayoutScene};
let mut plot = Plot::new();
let scene = LayoutScene::new()
.x_axis(Axis::new().range(vec![0, state.width() as i32]))
.y_axis(Axis::new().range(vec![0, state.height() as i32]))
.z_axis(Axis::new().range(vec![0, state.depth() as i32]))
.aspect_mode(AspectMode::Cube);
let layout = Layout::new()
.scene(scene);
plot.set_layout(layout);
let mut xv = Vec::new();
let mut yv = Vec::new();
let mut zv = Vec::new();
// let mut opacities = Vec::new();
let mut colors = Vec::new();
for z in 0..state.depth() {
if xv.len() >= 120000 {
add_scatter(&mut plot, &mut xv, &mut yv, &mut zv, &mut colors);
}
for y in 0..state.height() {
for x in 0..state.width() {
// if x%5 == 0 || y%5 == 0 || z%5 == 0 {
// continue;
// }
let cell = state.get(Index(Vec3u::new(x, y, z)));
if cell.e().mag() > 10.0 {
xv.push(x);
yv.push(y);
zv.push(z);
}
xv.push(x);
yv.push(y);
zv.push(z);
// opacities.push((cell.e().mag() * 0.1).min(1.0) as f64)
let mat = cell.mat().conductivity().mag() + if cell.mat().is_vacuum() {
0.0
} else {
5.0
};
//let g = scale_unsigned_to_u8(mat, 10.0);
//let r = scale_unsigned_to_u8(cell.mat().m().mag(), 100.0);
//let b = scale_unsigned_to_u8(cell.e().mag(), 1e2);
let r = scale_unsigned_to_u8(cell.mat().m().mag(), 100.0);
let g = scale_unsigned_to_u8(cell.e().mag(), 1e2);
let b = scale_unsigned_to_u8(mat, 10.0);
let alpha = 1.0;
colors.push(Rgba::new(r, g, b, alpha));
}
}
// let scatter = plotly::Scatter::new3(xv, yv, zv)
// .mode(plotly::common::Mode::Markers)
// .marker(plotly::common::Marker::new()
// //.opacity(0.2)
// //.size_array(sizes)
// //.opacity_array(opacities)
// .color_array(colors)
// );
// plot.add_trace(scatter);
}
let heat_map = HeatMap::new(xv, yv, zv);
plot.add_trace(heat_map);
let name = format!("frame{}", state.step_no());
plot.save(&*name, ImageFormat::PNG, state.width() as _, state.height() as _, 1.0);
add_scatter(&mut plot, &mut xv, &mut yv, &mut zv, &mut colors);
let name = format!("{}{}", self.out_base, state.step_no());
let (im_w, im_h) = im_size(state, 2048, 2048);
plot.save(&*name, ImageFormat::PNG, im_w as _, im_h as _, 1.0);
}
}
#[derive(Default)]
pub struct MultiRenderer {
renderers: Vec<Box<dyn Renderer>>,
renderers: RwLock<Vec<Box<dyn Renderer>>>,
}
impl MultiRenderer {
pub fn new() -> Self {
Default::default()
}
pub fn push<R: Renderer + 'static>(&mut self, r: R) {
self.renderers.push(Box::new(r));
pub fn push<R: Renderer + 'static>(&self, r: R) {
self.renderers.write().unwrap().push(Box::new(r));
}
pub fn with<R: Renderer + 'static>(mut self, r: R) -> Self {
self.push(r);
@@ -372,14 +450,14 @@ impl MultiRenderer {
}
impl Renderer for MultiRenderer {
fn render(&mut self, state: &dyn GenericSim, measurements: &[Box<dyn AbstractMeasurement>]) {
if self.renderers.len() != 0 {
fn render(&self, state: &dyn GenericSim, measurements: &[Box<dyn AbstractMeasurement>]) {
if self.renderers.read().unwrap().len() != 0 {
self.render_with_image(state, &RenderSteps::render(state, measurements, state.depth() / 2));
}
}
fn render_with_image(&mut self, state: &dyn GenericSim, im: &RgbImage) {
for r in &mut self.renderers {
fn render_with_image(&self, state: &dyn GenericSim, im: &RgbImage) {
for r in &*self.renderers.read().unwrap() {
r.render_with_image(state, im);
}
}

View File

@@ -1,6 +1,7 @@
use crate::{flt::{Flt, Real}, consts};
use crate::geom::{Coord, Index, Meters, Vec3, Vec3u};
use crate::mat::{self, GenericMaterial, Material};
use dyn_clone::{self, DynClone};
use log::trace;
use ndarray::{Array3, Zip};
@@ -8,7 +9,7 @@ use ndarray::parallel::prelude::*;
use std::convert::From;
use std::iter::Sum;
pub trait GenericSim {
pub trait GenericSim: Send + Sync + DynClone {
fn sample(&self, pos: Meters) -> Cell<mat::Static>;
fn impulse_e_meters(&mut self, pos: Meters, amount: Vec3);
fn impulse_h_meters(&mut self, pos: Meters, amount: Vec3);
@@ -29,7 +30,20 @@ pub trait GenericSim {
fn time(&self) -> Flt {
self.timestep() * self.step_no() as Flt
}
/// Take a "snapshot" of the simulation, dropping all material-specific information.
fn to_static(&self) -> SimState<mat::Static> {
let mut state = SimState::new(self.size(), self.feature_size());
Zip::from(ndarray::indices_of(&state.cells)).par_apply_assign_into(
&mut state.cells,
|(z, y, x)| {
let idx = Index((x as u32, y as u32, z as u32).into());
self.sample(idx.to_meters(self.feature_size()))
});
state
}
}
dyn_clone::clone_trait_object!(GenericSim);
impl<'a> dyn GenericSim + 'a {
pub fn get<C: Coord>(&self, at: C) -> Cell<mat::Static> {
@@ -82,7 +96,7 @@ impl<'a> dyn GenericSim + 'a {
}
}
#[derive(Default)]
#[derive(Default, Clone)]
pub struct SimState<M=GenericMaterial> {
cells: Array3<Cell<M>>,
scratch: Array3<Cell<M>>,
@@ -101,7 +115,7 @@ impl<M: Material + Default> SimState<M> {
}
}
impl<M: Material + Clone + Default + Send + Sync> SimState<M> {
impl<M: Material + Clone + Default + Send + Sync + 'static> SimState<M> {
pub fn step(&mut self) {
use consts::real::*;
let time_step = Real::from_inner(self.timestep());
@@ -134,7 +148,7 @@ impl<M: Material + Clone + Default + Send + Sync> SimState<M> {
}
}
impl<M: Material> GenericSim for SimState<M> {
impl<M: Material + Clone + Send + Sync + 'static> GenericSim for SimState<M> {
fn sample(&self, pos: Meters) -> Cell<mat::Static> {
// TODO: smarter sampling than nearest neighbor?
let pos_sim = pos.to_index(self.feature_size());