Files
fdtd-coremem/crates/coremem/examples/sr_latch.rs
colin 5b99d30cda restructure this multi-crate project to use Cargo's "workspace" feature
this solves an issue in the Nix build, where managing multiple
Cargo.lock files is otherwise tricky. it causes (or fails to fix?) an adjacent issue where
the spirv builder doesn't seem to have everything it needs vendored.
2022-07-05 17:34:21 -07:00

151 lines
8.1 KiB
Rust

/// this example creates a "set/reset" latch from a non-linear ferromagnetic device.
/// this is quite a bit like a "core memory" device.
/// the SR latch in this example is wired to a downstream latch, mostly to show that it's
/// possible to transfer the state (with some limitation) from one latch to another.
use coremem::{Driver, mat, meas, SpirvDriver};
use coremem::geom::{Meters, Torus};
use coremem::sim::spirv;
use coremem::sim::units::Seconds;
use coremem::stim::{CurlStimulus, Sinusoid1, TimeVarying as _};
fn main() {
coremem::init_logging();
// feature size: the side-length of each discrete grid cell to model (in Meters)
let feat_size = 20e-6f32;
// parameters used below to describe the components we construct below. units are (M, A or S).
let depth = 1600e-6;
// closest distance between the non-vacuum component and the dissipating boundary
// longer distances cause boundary reflections to be more dissipated (generally good).
let buffer_xy = 240e-6;
// length of our energy-dissipating boundary. longer distances decrease boundary reflections (good)
let boundary_xy = 500e-6;
let boundary_z = 300e-6;
// geometry parameters for the ferrite cores (modeled as torii)
let ferro_major = 320e-6;
let ferro_minor = 60e-6;
let ferro_buffer = 60e-6;
// geometry parameters for the coupling, drive, and sense wires (modeled as torii)
let wire_minor = 40e-6;
let wire_major = 160e-6;
let wire_coupling_major = 280e-6; // (ferro_minor*4 + ferro_buffer)/2 + wire_minor = 190
let peak_current = 7.5e6;
let current_duration = 6.0e-9; // half-wavelength of the sine wave
let drive_conductivity = 5e6f32;
let sense_conductivity = 5e3f32;
let half_depth = depth * 0.5;
// intermediate computed geometric parameters
let ferro_top_mid = boundary_xy + buffer_xy + wire_minor + wire_major;
let ferro_center_y = ferro_top_mid + ferro_major;
let ferro_bot_mid = ferro_center_y + ferro_major;
let height = ferro_bot_mid + wire_major + wire_minor + buffer_xy + boundary_xy;
let ferro1_left_edge = boundary_xy + buffer_xy;
let ferro1_center = ferro1_left_edge + ferro_minor + ferro_major;
let ferro1_right_edge = ferro1_center + ferro_major + ferro_minor;
let ferro2_left_edge = ferro1_right_edge + ferro_buffer;
let ferro2_center = ferro2_left_edge + ferro_minor + ferro_major;
let ferro2_right_edge = ferro2_center + ferro_major + ferro_minor;
let width = ferro2_right_edge + wire_major + wire_minor + buffer_xy + boundary_xy;
// create actual Regions from the computed parameters
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);
let sense_region = Torus::new_xz(Meters::new(ferro2_center + ferro_major, ferro_center_y, half_depth), wire_major, wire_minor);
let mut driver: SpirvDriver<spirv::FullyGenericMaterial> = Driver::new_spirv(Meters::new(width, height, depth), feat_size);
// mu_r=881.33, starting at H=25 to H=75.
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));
driver.fill_region(&sense_region, mat::IsomorphicConductor::new(sense_conductivity));
// fill the edge of the simulation with a graded conductor:
// on the inside of the simulation it matches vacuum (conductivity of 0), and
// ramps up conductivity (to dissipate energy) as it approaches the edge of the simulation.
driver.add_classical_boundary(Meters::new(boundary_xy, boundary_xy, boundary_z));
// helper to schedule a stimulus at the provided start time/duration.
let mut add_drive_pulse = |region: &Torus, start, duration, amp| {
let wave = Sinusoid1::from_wavelength(amp, duration * 2.0)
.half_cycle()
.shifted(start);
driver.add_stimulus(CurlStimulus::new(
region.clone(),
wave.clone(),
region.center(),
region.axis()
));
};
// stimuli apply some delta_E to the simulation, so we need to map our current to E:
// J=\sigma E
// dJ/dt = \sigma dE/dT
// dE/dt = dJ/dt / \sigma
// dE/dt = dI/dt / (A*\sigma)
// if I = k*sin(w t) then dE/dt = k*w cos(w t) / (A*\sigma)
// i.e. dE/dt is proportional to I/(A*\sigma), multiplied by w (or, divided by wavelength)
let peak_stim1 = peak_current/current_duration / (set_region.cross_section() * drive_conductivity);
// pulse the SET wire near the start of the simulation
add_drive_pulse(&set_region, 0.01*current_duration, current_duration, peak_stim1);
// RESET
add_drive_pulse(&reset_region, 4.0*current_duration, current_duration, peak_stim1);
// TOGGLE
add_drive_pulse(&set_region, 8.0*current_duration, current_duration, peak_stim1);
add_drive_pulse(&reset_region, 10.0*current_duration, current_duration, peak_stim1);
// TOGETHER
add_drive_pulse(&reset_region, 14.0*current_duration, current_duration, peak_stim1);
add_drive_pulse(&set_region, 14.0*current_duration, current_duration, peak_stim1);
// SET TWICE
add_drive_pulse(&set_region, 18.0*current_duration, current_duration, peak_stim1);
add_drive_pulse(&set_region, 20.0*current_duration, current_duration, peak_stim1);
let duration = 25.0*current_duration;
// 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()));
// XXX: if you change any parameters (above), then change this prefix. otherwise simulations
// will try to load state generated by an earlier run and use it to compute the current
// (differently-parameterized) run.
let prefix = "out/examples/sr-latch/";
let _ = std::fs::create_dir_all(&prefix);
// add a state file for easy resumption
driver.add_state_file(&*format!("{}state.bc", prefix), 9600);
// serialize frames for later viewing with `cargo run --release --bin viewer`
driver.add_serializer_renderer(&*format!("{}frame-", prefix), 36000, None);
// 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);
// how frequently to re-evaluate the stimulus (Sample & Hold interpolation between evaluations)
driver.set_steps_per_stim(1000);
driver.step_until(Seconds(duration));
}