diff --git a/README.md b/README.md index 2f87e9f..906ec3e 100644 --- a/README.md +++ b/README.md @@ -16,52 +16,122 @@ as the simulation progresses or completes. simulations are defined by creating either a binary crate which links against the `coremem` library, or simply inserting a top-level file to the `examples/` directory in this repo and running it. -here's an excerpt from the [sr_latch](examples/sr_latch.rs) example shipped in this library: +here's an excerpt from the [wavefront](examples/wavefront.rs) example shipped in this library: ```rust - // create some `Region` instances which will define our geometry - let ferro1_region = Torus::new_xy(Meters::new(ferro1_center, ferro_center_y, half_depth), ferro_major, ferro_minor); - let ferro2_region = Torus::new_xy(Meters::new(ferro2_center, ferro_center_y, half_depth), ferro_major, ferro_minor); - let set_region = Torus::new_yz(Meters::new(ferro1_center, ferro_center_y - ferro_major, half_depth), wire_major, wire_minor); - let reset_region = Torus::new_yz(Meters::new(ferro1_center, ferro_center_y + ferro_major, half_depth), wire_major, wire_minor); - let coupling_region = Torus::new_xz(Meters::new(0.5*(ferro1_center + ferro2_center), ferro_center_y, half_depth), wire_coupling_major, wire_minor); + // Create the simulation "driver" which uses the CPU as backend. + let mut driver: driver::CpuDriver = driver::Driver::new(size, feature_size); - // create the interface through which we'll "drive" a simulation to completion: - let mut driver: SpirvDriver = Driver::new_spirv(Meters::new(width, height, depth), feat_size); + // create a conductor on the left side. + let conductor = Cube::new( + Index::new(0, 0, 0).to_meters(feature_size), + Index::new(width/10, height, 1).to_meters(feature_size), + ); + driver.fill_region(&conductor, mat::IsomorphicConductor::new(200f32)); - // fill each region within the simulation with the appropriate material - driver.fill_region(&ferro1_region, mat::MHPgram::new(25.0, 881.33, 44000.0)); - driver.fill_region(&ferro2_region, mat::MHPgram::new(25.0, 881.33, 44000.0)); - driver.fill_region(&set_region, mat::IsomorphicConductor::new(drive_conductivity)); - driver.fill_region(&reset_region, mat::IsomorphicConductor::new(drive_conductivity)); - driver.fill_region(&coupling_region, mat::IsomorphicConductor::new(drive_conductivity)); + // create a vertical strip in the center of the simulation which emits a wave. + let center_region = Cube::new( + Index::new(200, height/4, 0).to_meters(feature_size), + Index::new(201, height*3/4, 1).to_meters(feature_size), + ); + // emit a constant E/H delta over this region for 100 femtoseconds + let stim = Stimulus::new( + center_region, + UniformStimulus::new( + Vec3::new(2e19, 0.0, 0.0), // E field (per second) + Vec3::new(0.0, 0.0, 2e19/376.730) // H field (per second) + ).gated(0.0, 100e-15), + ); + driver.add_stimulus(stim); - // populate our stimuli. - // here we pulse the E field with amplitude defined by a half-sine wave w.r.t. time. - // we apply this to the "set" wire loop, everywhere directed tangential to the loop. - let wave = Sinusoid1::from_wavelength(peak_set_field, set_pulse_duration * 2.0) - .half_cycle() - .shifted(start); - driver.add_stimulus(CurlStimulus::new( - set_region.clone(), - wave, - set_region.center(), - set_region.axis() - )); - - // every 36000 "steps", render the simulation state to disk. - driver.add_serializer_renderer("out/examples/sr_latch/frame-", 36000, None); - driver.step_until(Seconds(3.0*set_pulse_duration)); + // finally, run the simulation: + driver.step_until(Seconds(100e-12)); ``` you can run the full example with: +``` +$ cargo run --release --example wavefront +``` + +graphical simulation output is displayed directly to the terminal, and also rendered (in higher resolution) to +a video file in `out/examples/wavefront/`. + +this example runs on the CPU. the moment you do anything more complex than this, you'll want to leverage the GPU, +which can easily be 40-100x faster: + + +## GPU Acceleration + +we use rust-gpu for gpu acceleration. presently, this requires *specific* versions of rust-nightly to work: + +``` +$ rustup default nightly-2022-01-13 +$ rustup component add rust-src rustc-dev llvm-tools-preview +``` + +(it's possible to work with older nightlies like `nightly-2021-06-08` if you enable the 2020 feature.) + +now you can swap out the `CpuDriver` with a `SpirvDriver` and you're set: +```diff +- let mut driver: driver::CpuDriver = driver::Driver::new(size, feature_size); ++ let mut driver: driver::SpirvDriver = driver::Driver::new_spirv(size, feature_size); +``` + +re-run it as before and you should see the same results: + +``` +$ cargo run --release --example wavefront +``` + +see the "Processing Loop" section below to understand what GPU acceleration entails. + +## Advanced Usage: Viewers, Measurements + +the [sr\_latch](examples/sr_latch.rs) example explores a more interesting feature set. +first, it "measures" a bunch of parameters over different regions of the simulation +(peak inside `src/meas.rs` to see how these each work): + +```rust +// measure a bunch of items of interest throughout the whole simulation duration: +driver.add_measurement(meas::CurrentLoop::new("coupling", coupling_region.clone())); +driver.add_measurement(meas::Current::new("coupling", coupling_region.clone())); +driver.add_measurement(meas::CurrentLoop::new("sense", sense_region.clone())); +driver.add_measurement(meas::Current::new("sense", sense_region.clone())); +driver.add_measurement(meas::MagneticLoop::new("mem1", ferro1_region.clone())); +driver.add_measurement(meas::Magnetization::new("mem1", ferro1_region.clone())); +driver.add_measurement(meas::MagneticFlux::new("mem1", ferro1_region.clone())); +driver.add_measurement(meas::MagneticLoop::new("mem2", ferro2_region.clone())); +driver.add_measurement(meas::CurrentLoop::new("set", set_region.clone())); +driver.add_measurement(meas::Current::new("set", set_region.clone())); +driver.add_measurement(meas::Power::new("set", set_region.clone())); +driver.add_measurement(meas::CurrentLoop::new("reset", reset_region.clone())); +``` + +during the simulation, it dumps these measurements into a CSV: + +```rust +// render a couple CSV files: one very detailed and the other more sparsely detailed +driver.add_csv_renderer(&*format!("{}meas.csv", prefix), 200, None); +driver.add_csv_renderer(&*format!("{}meas-sparse.csv", prefix), 1600, None); +``` + +furthermore, it serializes point-in-time snapshots of the simulation while it's running, +allowing you to dig further into the simulation in an _interactive_ way (versus the raw video +renderer used in the `wavefront` example): + +```rust +// serialize frames for later viewing with `cargo run --release --bin viewer` +driver.add_serializer_renderer(&*format!("{}frame-", prefix), 36000, None); +``` + +run this, after having setup the GPU pre-requisites: + ``` $ cargo run --release --example sr_latch ``` -TODO: switch between CPU and GPU accel in this demo. - and then investigate the results with + ``` $ cargo run --bin viewer out/examples/sr_latch ``` @@ -85,7 +155,7 @@ here's a plot of `M(mem2)` over time from the SR latch simulation. we're measuri ![plot of M(mem2) over time](readme_images/sr_latch_vd_M2.png "plot of M(mem2) over time") -## Processing Loop +## Processing Loop (and how GPU acceleration works) the processing loop for a simulation is roughly as follows (src/driver.rs:step_until drives this loop): 1. evaluate all stimuli at the present moment in time; these produce an "externally applied" E and B field @@ -104,20 +174,6 @@ it turns out that the Courant rules force us to evaluate FDTD updates (step 2) o as a result, step 2 is actually able to apply the FDTD update functions not just once but up to `min(N, M, Z)` times. although steps 1 and 3 vary heavily based on the user configuration of the simulation, step 2 can be defined pretty narrowly in code (no user-callbacks/dynamic function calls/etc). this lets us offload the processing of step 2 to a dedicated GPU. by tuning N/M/Z, step 2 becomes the dominant cost in our simulations an GPU offloading can trivially boost performance by more than an order of magnitude on even a mid-range consumer GPU. - -## GPU Acceleration - -we use rust-gpu for gpu acceleration. presently, this requires *specific* versions of rust-nightly to work: - -``` -$ rustup default nightly-2022-01-13 -$ rustup component add rust-src rustc-dev llvm-tools-preview -``` - -(it's possible to work with older nightlies like `nightly-2021-06-08` if you enable the 2020 feature.) - -TODO: show how to enable gpu accel. - # Features TODO: document Material options, Stimulus options, Measurement options, Rendering options. @@ -133,18 +189,18 @@ this is the "default" optimized version. you could introduce a new material to t contrast that to the CPU-only implementation which achieves 24.6M grid cell steps per second: that's about a 34x gain. + # Support the author can be reached on Matrix <@colin:uninsane.org> or Activity Pub <@colin@fed.uninsane.org>. i poured a lot of time into making this: i'm happy to spend the marginal extra time to help curious people make use of what i've made, so don't hesitate to reach out. + ## Additional Resources TODO: cite the works which were useful in getting this off the ground. -TODO: consult the licenses of my dependencies. - -# License +## License i'm not a lawyer, and i don't want to be. by nature of your reading this, my computer has freely shared these bits with yours.