README: clean up the pieces which refer to examples
This commit is contained in:
158
README.md
158
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,
|
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.
|
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
|
```rust
|
||||||
// create some `Region` instances which will define our geometry
|
// Create the simulation "driver" which uses the CPU as backend.
|
||||||
let ferro1_region = Torus::new_xy(Meters::new(ferro1_center, ferro_center_y, half_depth), ferro_major, ferro_minor);
|
let mut driver: driver::CpuDriver = driver::Driver::new(size, feature_size);
|
||||||
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 interface through which we'll "drive" a simulation to completion:
|
// create a conductor on the left side.
|
||||||
let mut driver: SpirvDriver<spirv::FullyGenericMaterial> = Driver::new_spirv(Meters::new(width, height, depth), feat_size);
|
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
|
// create a vertical strip in the center of the simulation which emits a wave.
|
||||||
driver.fill_region(&ferro1_region, mat::MHPgram::new(25.0, 881.33, 44000.0));
|
let center_region = Cube::new(
|
||||||
driver.fill_region(&ferro2_region, mat::MHPgram::new(25.0, 881.33, 44000.0));
|
Index::new(200, height/4, 0).to_meters(feature_size),
|
||||||
driver.fill_region(&set_region, mat::IsomorphicConductor::new(drive_conductivity));
|
Index::new(201, height*3/4, 1).to_meters(feature_size),
|
||||||
driver.fill_region(&reset_region, mat::IsomorphicConductor::new(drive_conductivity));
|
);
|
||||||
driver.fill_region(&coupling_region, mat::IsomorphicConductor::new(drive_conductivity));
|
// 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.
|
// finally, run the simulation:
|
||||||
// here we pulse the E field with amplitude defined by a half-sine wave w.r.t. time.
|
driver.step_until(Seconds(100e-12));
|
||||||
// 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));
|
|
||||||
```
|
```
|
||||||
|
|
||||||
you can run the full example with:
|
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
|
$ cargo run --release --example sr_latch
|
||||||
```
|
```
|
||||||
|
|
||||||
TODO: switch between CPU and GPU accel in this demo.
|
|
||||||
|
|
||||||
and then investigate the results with
|
and then investigate the results with
|
||||||
|
|
||||||
```
|
```
|
||||||
$ cargo run --bin viewer out/examples/sr_latch
|
$ 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
|
|||||||
 over time")
|
 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):
|
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
|
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.
|
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.
|
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
|
# Features
|
||||||
|
|
||||||
TODO: document Material options, Stimulus options, Measurement options, Rendering options.
|
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.
|
contrast that to the CPU-only implementation which achieves 24.6M grid cell steps per second: that's about a 34x gain.
|
||||||
|
|
||||||
|
|
||||||
# Support
|
# 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
|
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.
|
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
|
## Additional Resources
|
||||||
|
|
||||||
TODO: cite the works which were useful in getting this off the ground.
|
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.
|
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.
|
by nature of your reading this, my computer has freely shared these bits with yours.
|
||||||
|
Reference in New Issue
Block a user