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.
This commit is contained in:
2022-07-05 17:34:21 -07:00
parent d3cd12aa47
commit 5b99d30cda
64 changed files with 108 additions and 206 deletions

View File

@@ -0,0 +1,116 @@
#![cfg_attr(
target_arch = "spirv",
feature(register_attr),
register_attr(spirv),
no_std
)]
#![feature(const_fn_floating_point_arithmetic)]
extern crate spirv_std;
pub use glam::{UVec3, Vec3};
use spirv_std::glam;
#[cfg(not(target_arch = "spirv"))]
use spirv_std::macros::spirv;
pub mod mat;
pub mod sim;
pub mod support;
pub use sim::{SerializedSimMeta, SerializedStepE, SerializedStepH};
pub use support::{Optional, UnsizedArray, UVec3Std, Vec3Std};
use mat::{IsoConductorOr, Ferroxcube3R1MH, FullyGenericMaterial, Material};
type Iso3R1 = IsoConductorOr<Ferroxcube3R1MH>;
fn step_h<M: Material>(
id: UVec3,
meta: &SerializedSimMeta,
stimulus_h: &UnsizedArray<Vec3Std>,
material: &UnsizedArray<M>,
e: &UnsizedArray<Vec3Std>,
h: &mut UnsizedArray<Vec3Std>,
m: &mut UnsizedArray<Vec3Std>,
) {
if id.x < meta.dim.x() && id.y < meta.dim.y() && id.z < meta.dim.z() {
let sim_state = SerializedStepH::new(meta, stimulus_h, material, e, h, m);
let update_state = sim_state.index(id);
update_state.step_h();
}
}
fn step_e<M: Material>(
id: UVec3,
meta: &SerializedSimMeta,
stimulus_e: &UnsizedArray<Vec3Std>,
material: &UnsizedArray<M>,
e: &mut UnsizedArray<Vec3Std>,
h: &UnsizedArray<Vec3Std>,
) {
if id.x < meta.dim.x() && id.y < meta.dim.y() && id.z < meta.dim.z() {
let sim_state = SerializedStepE::new(meta, stimulus_e, material, e, h);
let update_state = sim_state.index(id);
update_state.step_e();
}
}
/// Return the step_h/step_e entry point names for the provided material
pub fn entry_points<M: 'static>() -> Optional<(&'static str, &'static str)> {
use core::any::TypeId;
let mappings = [
(TypeId::of::<FullyGenericMaterial>(),
("step_h_generic_material", "step_e_generic_material")
),
(TypeId::of::<Iso3R1>(),
("step_h_iso_3r1", "step_e_iso_3r1")
),
];
for (id, names) in mappings {
if id == TypeId::of::<M>() {
return Optional::some(names);
}
}
Optional::none()
}
macro_rules! steps {
($mat:ty, $step_h:ident, $step_e:ident) => {
// LocalSize/numthreads
#[spirv(compute(threads(4, 4, 4)))]
pub fn $step_h(
#[spirv(global_invocation_id)] id: UVec3,
#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] meta: &SerializedSimMeta,
// XXX: delete this input?
#[spirv(storage_buffer, descriptor_set = 0, binding = 1)] _unused_stimulus_e: &UnsizedArray<Vec3Std>,
#[spirv(storage_buffer, descriptor_set = 0, binding = 2)] stimulus_h: &UnsizedArray<Vec3Std>,
#[spirv(storage_buffer, descriptor_set = 0, binding = 3)] material: &UnsizedArray<$mat>,
#[spirv(storage_buffer, descriptor_set = 0, binding = 4)] e: &UnsizedArray<Vec3Std>,
#[spirv(storage_buffer, descriptor_set = 0, binding = 5)] h: &mut UnsizedArray<Vec3Std>,
#[spirv(storage_buffer, descriptor_set = 0, binding = 6)] m: &mut UnsizedArray<Vec3Std>,
) {
step_h(id, meta, stimulus_h, material, e, h, m)
}
#[spirv(compute(threads(4, 4, 4)))]
pub fn $step_e(
#[spirv(global_invocation_id)] id: UVec3,
#[spirv(storage_buffer, descriptor_set = 0, binding = 0)] meta: &SerializedSimMeta,
#[spirv(storage_buffer, descriptor_set = 0, binding = 1)] stimulus_e: &UnsizedArray<Vec3Std>,
// XXX: delete this input?
#[spirv(storage_buffer, descriptor_set = 0, binding = 2)] _unused_stimulus_h: &UnsizedArray<Vec3Std>,
#[spirv(storage_buffer, descriptor_set = 0, binding = 3)] material: &UnsizedArray<$mat>,
#[spirv(storage_buffer, descriptor_set = 0, binding = 4)] e: &mut UnsizedArray<Vec3Std>,
#[spirv(storage_buffer, descriptor_set = 0, binding = 5)] h: &UnsizedArray<Vec3Std>,
// XXX: can/should this m input be deleted?
#[spirv(storage_buffer, descriptor_set = 0, binding = 6)] _unused_m: &UnsizedArray<Vec3Std>,
) {
step_e(id, meta, stimulus_e, material, e, h)
}
};
}
steps!(FullyGenericMaterial, step_h_generic_material, step_e_generic_material);
steps!(Iso3R1, step_h_iso_3r1, step_e_iso_3r1);

View File

@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}

View File

@@ -0,0 +1,393 @@
use crate::support::{Optional, Vec3Std};
pub trait Material: Sized {
fn conductivity(&self) -> Vec3Std {
Default::default()
}
/// returns the new M vector for this material
fn move_b_vec(&self, m: Vec3Std, _target_b: Vec3Std) -> Vec3Std {
// XXX could return either 0, or `m`. they should be the same, but one might be more
// optimizable than the other (untested).
m
}
}
#[derive(Copy, Clone, Default, PartialEq)]
pub struct Conductor<T>(pub T);
pub type AnisomorphicConductor = Conductor<Vec3Std>;
pub type IsomorphicConductor = Conductor<f32>;
impl Material for AnisomorphicConductor {
fn conductivity(&self) -> Vec3Std {
self.0
}
}
impl Material for IsomorphicConductor {
fn conductivity(&self) -> Vec3Std {
Vec3Std::uniform(self.0)
}
}
/// M(B) parallelogram
///
///```ignore
/// ____________
/// / /
/// / . /
/// / /
/// /___________/
/// ```
///
/// The `.` depicts (0, 0). X axis is B; y axis is M.
/// As B increases, M remains constant until it hits an edge.
/// Then M rises up to its max.
/// Same thing happens on the left edge, as B decreases and M falls to its min.
#[derive(Copy, Clone, Default, PartialEq)]
pub struct MBPgram {
/// X coordinate at which the upward slope starts
pub b_start: f32,
/// X coordinate at which the upward slope ends
pub b_end: f32,
/// Vertical range of the graph
pub max_m: f32,
}
/// f(x0) = -ymax
/// f(x1) = ymax
fn eval_bounded_line(x: f32, x0: f32, x1: f32, ymax: f32) -> f32 {
let x = x - x0;
let x1 = x1 - x0;
// Now, f(0) = -ymax, f(x1) = ymax
let x = x.max(0.0).min(x1);
ymax * (2.0*x/x1 - 1.0)
}
impl MBPgram {
pub fn new(b_start: f32, b_end: f32, max_m: f32) -> Self {
Self { b_start, b_end, max_m }
}
/// Return the new `M`
pub fn move_b(self, m: f32, target_b: f32) -> f32 {
// Evaluate the curve on the right:
let y_right = eval_bounded_line(target_b, self.b_start, self.b_end, self.max_m);
let y_left = eval_bounded_line(target_b, -self.b_end, -self.b_start, self.max_m);
m.max(y_right).min(y_left)
}
}
impl Material for MBPgram {
fn move_b_vec(&self, m: Vec3Std, target_b: Vec3Std) -> Vec3Std {
Vec3Std::new(
self.move_b(m.x(), target_b.x()),
self.move_b(m.y(), target_b.y()),
self.move_b(m.z(), target_b.z()),
)
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct MHPgram {
/// optimized form of mu_0^-1 * (1-mu_r^-1)
b_mult: f32,
/// optimized form of h_intercept * (1-mu_r^-1)
m_offset: f32,
/// Vertical range of the graph
pub max_m: f32,
}
impl MHPgram {
/// h_intercept: X coordinate at which M is always zero.
/// mu_r: relative mu value along the non-flat edges of the parallelogram.
pub const fn new(h_intercept: f32, mu_r: f32, max_m: f32) -> Self {
const MU0_INV: f32 = 795774.715025073;
let one_minus_mu_r_inv = 1.0 - 1.0/mu_r;
Self {
b_mult: MU0_INV * one_minus_mu_r_inv,
m_offset: h_intercept * one_minus_mu_r_inv,
max_m,
}
}
pub fn h_intercept(&self) -> f32 {
const MU0_INV: f32 = 795774.715025073;
MU0_INV * self.m_offset / self.b_mult
}
pub fn mu_r(&self) -> f32 {
const MU0: f32 = 1.2566370621219e-06;
let mu_r_inv = 1.0 - MU0*self.b_mult;
1.0 / mu_r_inv
}
/// Return the new `M`
pub fn move_b(self, m: f32, target_b: f32) -> f32 {
// The point may exist outside the parallelogram.
// The right slope is defined by:
// B(H)/mu0 = h_intercept + (h-h_intercept)*mu_r
// "unoptimized" solution:
// let target_mh = target_b*MU0_INV;
// let h_on_right = (target_mh-self.h_intercept)/self.mu_r + self.h_intercept;
// let m_on_right = target_mh - h_on_right;
// Left:
// B(H)/mu0 = -h_intercept + (h+h_intercept)*mu_r
// "unoptimized" solution:
// let h_on_left = (target_mh+self.h_intercept)/self.mu_r - self.h_intercept;
// let m_on_left = target_mh - h_on_left;
let m_on_right = target_b*self.b_mult - self.m_offset;
let m_on_left = target_b*self.b_mult + self.m_offset;
if m_on_right >= m {
// if m_on_right <= self.max_m {
// // rightward edge movement
// m_on_right
// } else {
// // right of saturation
// self.max_m
// }
m_on_right.min(self.max_m)
} else if m_on_left <= m {
// if m_on_left >= -self.max_m {
// // leftward edge movement
// m_on_left
// } else {
// // left of saturation
// -self.max_m
// }
m_on_left.max(-self.max_m)
} else {
// interior movement
m
}
// this boils down to:
// m.clamp(m_on_left, m_on_right).clamp(-max_m, max_m)
}
}
impl Material for MHPgram {
fn move_b_vec(&self, m: Vec3Std, target_b: Vec3Std) -> Vec3Std {
Vec3Std::new(
self.move_b(m.x(), target_b.x()),
self.move_b(m.y(), target_b.y()),
self.move_b(m.z(), target_b.z()),
)
}
}
#[derive(Copy, Clone, Default, PartialEq)]
pub struct FullyGenericMaterial {
pub conductivity: Vec3Std,
pub m_b_curve: Optional<MBPgram>,
pub m_h_curve: Optional<MHPgram>,
}
impl Material for FullyGenericMaterial {
fn conductivity(&self) -> Vec3Std {
self.conductivity
}
fn move_b_vec(&self, m: Vec3Std, target_b: Vec3Std) -> Vec3Std {
if self.m_b_curve.is_some() {
self.m_b_curve.unwrap().move_b_vec(m, target_b)
} else if self.m_h_curve.is_some() {
self.m_h_curve.unwrap().move_b_vec(m, target_b)
} else {
Default::default()
}
}
}
/// MHPgram that's vaguely similar to Ferroxcube's 3R1 material
#[derive(Copy, Clone, Default, PartialEq)]
pub struct Ferroxcube3R1MH;
impl Into<MHPgram> for Ferroxcube3R1MH {
fn into(self) -> MHPgram {
const CURVE: MHPgram = MHPgram::new(25.0, 881.33, 44000.0);
CURVE
}
}
impl Material for Ferroxcube3R1MH {
fn move_b_vec(&self, m: Vec3Std, target_b: Vec3Std) -> Vec3Std {
let curve: MHPgram = (*self).into();
curve.move_b_vec(m, target_b)
}
}
/// Optimized material aimed at saving space for the common case of a simulation that contains
/// isomorphic conductors plus one exception material.
#[derive(Copy, Clone, Default, PartialEq)]
pub struct IsoConductorOr<M> {
/// conductivity, if >= 0, else ignored & used to signal the other material
pub value: f32,
pub mat: M,
}
impl<M: Default> IsoConductorOr<M> {
pub fn new_conductor(conductivity: f32) -> Self {
Self {
value: conductivity,
mat: Default::default(),
}
}
pub fn new_other() -> Self {
Self {
value: -1.0,
mat: Default::default()
}
}
}
impl<M: Material + Copy> Material for IsoConductorOr<M> {
fn conductivity(&self) -> Vec3Std {
Vec3Std::uniform(self.value.max(0.0))
}
fn move_b_vec(&self, m: Vec3Std, target_b: Vec3Std) -> Vec3Std {
if self.value < 0.0 {
let mat = self.mat; //< XXX hack for ZST
mat.move_b_vec(m, target_b)
} else {
m
}
}
}
#[cfg(test)]
mod test {
use super::*;
fn assert_eq_approx(actual: f32, expected: f32, max_error: f32) {
assert!((actual - expected).abs() < max_error, "{} != {}", actual, expected);
}
fn assert_eq_approx_tight(actual: f32, expected: f32) {
assert_eq_approx(actual, expected, 1e-5);
}
#[test]
fn mb_curve_interior() {
let curve = MBPgram::new(4.0, 6.0, 20.0f32);
assert_eq_approx_tight(curve.move_b(0.0, 2.0), 0.0);
assert_eq_approx_tight(curve.move_b(0.0, 5.0), 0.0);
assert_eq_approx_tight(curve.move_b(1.0, 5.0), 1.0);
assert_eq_approx_tight(curve.move_b(20.0, 5.0), 20.0);
assert_eq_approx_tight(curve.move_b(-20.0, 4.0), -20.0);
assert_eq_approx_tight(curve.move_b(-20.0, -6.0), -20.0);
assert_eq_approx_tight(curve.move_b(20.0, -4.0), 20.0);
assert_eq_approx_tight(curve.move_b(10.0, -2.0), 10.0);
}
#[test]
fn mb_curve_exterior() {
let curve = MBPgram::new(4.0, 6.0, 20.0f32);
assert_eq_approx_tight(curve.move_b(0.0, 6.0), 20.0);
assert_eq_approx_tight(curve.move_b(0.0, 7.0), 20.0);
assert_eq_approx_tight(curve.move_b(0.0, -6.0), -20.0);
assert_eq_approx_tight(curve.move_b(0.0, -7.0), -20.0);
assert_eq_approx_tight(curve.move_b(2.0, -6.0), -20.0);
assert_eq_approx_tight(curve.move_b(20.0, -6.0), -20.0);
assert_eq_approx_tight(curve.move_b(20.0, -5.0), 0.0);
assert_eq_approx_tight(curve.move_b(20.0, -4.5), 10.0);
assert_eq_approx_tight(curve.move_b(-15.0, 4.5), -10.0);
assert_eq_approx_tight(curve.move_b(-15.0, 5.0), 0.0);
assert_eq_approx_tight(curve.move_b(-15.0, 5.5), 10.0);
assert_eq_approx_tight(curve.move_b(-15.0, 7.5), 20.0);
}
#[test]
fn mb_curve_3r1() {
// slope of 3r1 is about M=793210*B
// This is almost identical to iron (795615)!
let curve = MBPgram::new(-0.3899, 0.3900, 310000f32);
// magnetizing:
// v.s. 198893 in B(H) curve
assert_eq_approx(curve.move_b(0.0, 0.250), 198703.0, 1.0);
// v.s. 278321 in B(H) curve
assert_eq_approx(curve.move_b(198703.0, 0.350), 278201.0, 1.0);
assert_eq_approx(curve.move_b(278201.0, 0.390), 310000.0, 1.0);
// de-magnetizing:
// From saturation, decreasing B causes NO decrease in M: instead, it causes a decrease in
// H. This is probably BAD: in the B(H) curve, a large change in H always causes a large
// change in B. The movement of H here is likely to induce current, whereas it SHOULDN'T.
assert_eq_approx(curve.move_b(310000.0, 0.38995), 310000.0, 1.0);
// v.s. 258626 in B(H); H = 220
assert_eq_approx(curve.move_b(310000.0, 0.325), 258406.0, 1.0);
// here's where H crosses 0 (v.s. B=0.325 in the B(H) curve... quite a difference)
assert_eq_approx(curve.move_b(310000.0, 0.050), 39788.438, 1.0);
// v.s. 35.0 in B(H)
assert_eq_approx(curve.move_b(310000.0, 0.0), 39.75, 1.0);
// negative magnetization:
// erase the magnetization: H = -40
assert_eq_approx(curve.move_b(310000.0, -0.00005), 0.0, 1.0);
// the magnetization has been completely erased:
assert_eq_approx(curve.move_b(310000.0, -0.25), -198703.0, 1.0);
}
#[test]
fn mh_curve_parameters() {
let curve = MHPgram::new(50.0, 101.0, 500.0);
assert_eq_approx(curve.h_intercept(), 50.0, 1e-3);
assert_eq_approx(curve.mu_r(), 101.0, 1e-3);
}
#[test]
fn mh_curve_edge_travel() {
const MU0: f32 = 1.2566370621219e-06;
let curve = MHPgram::new(50.0, 101.0, 500.0);
assert_eq_approx(curve.move_b(0.0, 151.0*MU0), 100.0, 0.1); // rightward travel along edge
assert_eq_approx(curve.move_b(100.0, 252.0*MU0), 200.0, 0.1);
assert_eq_approx(curve.move_b(200.0, 555.0*MU0), 500.0, 0.1);
assert_eq_approx(curve.move_b(500.0, 500.0*MU0), 500.0, 0.1); // back (leftward) travel to H=0
assert_eq_approx(curve.move_b(500.0, 455.0*MU0), 500.0, 0.1); // back (leftward) travel to H=-45
assert_eq_approx(curve.move_b(500.0, 354.0*MU0), 400.0, 0.1); // back (leftward) travel to H=-46
assert_eq_approx(curve.move_b(400.0, 253.0*MU0), 300.0, 0.1); // back (leftward) travel to H=-47
assert_eq_approx(curve.move_b(300.0, -50.0*MU0), 0.0, 0.1); // back (leftward) travel to H=-50
assert_eq_approx(curve.move_b(0.0, -151.0*MU0), -100.0, 0.1); // back (leftward) travel to H=-51
assert_eq_approx(curve.move_b(-100.0, -555.0*MU0), -500.0, 0.1); // back (leftward) travel to H=-55
assert_eq_approx(curve.move_b(-500.0, -456.0*MU0), -500.0, 0.1); // interior (rightward) travel to H=44
assert_eq_approx(curve.move_b(-500.0, -354.0*MU0), -400.0, 0.1); // interior (rightward) travel to H=46
assert_eq_approx(curve.move_b(-400.0, -253.0*MU0), -300.0, 0.1); // interior (rightward) travel to H=47
assert_eq_approx(curve.move_b(-300.0, 50.0*MU0), 0.0, 0.1); // interior (rightward) travel to H=50
}
#[test]
fn mh_curve_interior() {
const MU0: f32 = 1.2566370621219e-06;
let curve = MHPgram::new(50.0, 101.0, 500.0);
// max M (right) happens at H=55; B = 555*MU0;
// min M (right) happens at H=45; B = -455*MU0;
// max M (left) happens at H=-45; B = 455*MU0;
// min M (left) happens at H=-55; B = -555*MU0;
assert_eq_approx(curve.move_b(0.0, 40.0*MU0), 0.0, 0.1);
assert_eq_approx(curve.move_b(0.0, 50.0*MU0), 0.0, 0.1);
assert_eq_approx(curve.move_b(0.0, -40.0*MU0), 0.0, 0.1);
assert_eq_approx(curve.move_b(0.0, -50.0*MU0), 0.0, 0.1);
assert_eq_approx(curve.move_b(-400.0, -400.0*MU0), -400.0, 0.1); // rightward travel from H=-54 to NOT H=-53.5
assert_eq_approx(curve.move_b(-400.0, -355.0*MU0), -400.0, 0.1); // rightward travel from H=-54 to H=45
assert_eq_approx(curve.move_b(-400.0, -354.0*MU0), -400.0, 0.1); // rightward travel from H=-54 to H=46
assert_eq_approx(curve.move_b(-400.0, 5000.0*MU0), 500.0, 0.1); // rightward travel from H=-54 to H>>55
}
#[test]
fn mh_curve_exterior() {
const MU0: f32 = 1.2566370621219e-06;
let curve = MHPgram::new(50.0, 101.0, 500.0);
assert_eq_approx(curve.move_b(500.0, 556.0*MU0), 500.0, 0.1); // exterior travel to H=56
assert_eq_approx(curve.move_b(500.0, 5000.0*MU0), 500.0, 0.1); // exterior travel to H>>55
assert_eq_approx(curve.move_b(500.0, -5000.0*MU0), -500.0, 0.1); // exterior travel from H>>55 to H << -55
assert_eq_approx(curve.move_b(-501.0, 454.0*MU0), 400.0, 0.1); // exterior travel from H<<-55 (rounding error) to H=54
}
}

View File

@@ -0,0 +1,343 @@
// use spirv_std::RuntimeArray;
use spirv_std::glam::UVec3;
use crate::mat::Material;
use crate::support::{Array3, Array3Mut, ArrayHandle, ArrayHandleMut, Optional, UnsizedArray, UVec3Std, Vec3Std};
#[derive(Copy, Clone)]
pub struct SerializedSimMeta {
// XXX this HAS to be the tuple version instead of the glam dim, else:
// OpLoad Pointer <id> '138[%138]' is not a logical pointer.
pub dim: UVec3Std,
pub inv_feature_size: f32,
pub time_step: f32,
pub feature_size: f32,
}
impl SerializedSimMeta {
fn dim(&self) -> UVec3 {
self.dim.into()
}
}
/// Whatever data we received from the host in their call to step_h
pub struct SerializedStepH<'a, M> {
meta: &'a SerializedSimMeta,
stimulus_h: &'a UnsizedArray<Vec3Std>,
material: &'a UnsizedArray<M>,
e: &'a UnsizedArray<Vec3Std>,
h: &'a mut UnsizedArray<Vec3Std>,
m: &'a mut UnsizedArray<Vec3Std>,
}
impl<'a, M> SerializedStepH<'a, M> {
pub fn new(
meta: &'a SerializedSimMeta,
stimulus_h: &'a UnsizedArray<Vec3Std>,
material: &'a UnsizedArray<M>,
e: &'a UnsizedArray<Vec3Std>,
h: &'a mut UnsizedArray<Vec3Std>,
m: &'a mut UnsizedArray<Vec3Std>,
) -> Self {
Self {
meta, stimulus_h, material, e, h, m
}
}
pub fn index(self, idx: UVec3) -> StepHContext<'a, M> {
let dim = self.meta.dim();
let stim_h_matrix = Array3::new(self.stimulus_h, dim);
let mat_matrix = Array3::new(self.material, dim);
let e = Array3::new(self.e, dim);
let h = Array3Mut::new(self.h, dim);
let m = Array3Mut::new(self.m, dim);
let in_e = VolumeSamplePos {
mid: e.get(idx).unwrap(),
xp1: e.get(idx + UVec3::X),
yp1: e.get(idx + UVec3::Y),
zp1: e.get(idx + UVec3::Z),
};
let out_h = h.into_mut_handle(idx);
let out_m = m.into_mut_handle(idx);
let mat = mat_matrix.into_handle(idx);
StepHContext {
inv_feature_size: self.meta.inv_feature_size,
time_step: self.meta.time_step,
stim_h: stim_h_matrix.get(idx).unwrap(),
mat,
in_e,
out_h,
out_m,
}
}
}
/// Whatever data we received from the host in their call to step_e
pub struct SerializedStepE<'a, M> {
meta: &'a SerializedSimMeta,
stimulus_e: &'a UnsizedArray<Vec3Std>,
material: &'a UnsizedArray<M>,
e: &'a mut UnsizedArray<Vec3Std>,
h: &'a UnsizedArray<Vec3Std>,
}
impl<'a, M> SerializedStepE<'a, M> {
pub fn new(
meta: &'a SerializedSimMeta,
stimulus_e: &'a UnsizedArray<Vec3Std>,
material: &'a UnsizedArray<M>,
e: &'a mut UnsizedArray<Vec3Std>,
h: &'a UnsizedArray<Vec3Std>,
) -> Self {
Self {
meta, stimulus_e, material, e, h
}
}
pub fn index(self, idx: UVec3) -> StepEContext<'a, M> {
let dim = self.meta.dim();
let stim_e_matrix = Array3::new(self.stimulus_e, dim);
let mat_matrix = Array3::new(self.material, dim);
let e = Array3Mut::new(self.e, dim);
let h = Array3::new(self.h, dim);
let xm1 = if idx.x == 0 {
Optional::none()
} else {
h.get(idx - UVec3::X)
};
let ym1 = if idx.y == 0 {
Optional::none()
} else {
h.get(idx - UVec3::Y)
};
let zm1 = if idx.z == 0 {
Optional::none()
} else {
h.get(idx - UVec3::Z)
};
let in_h = VolumeSampleNeg {
mid: h.get(idx).unwrap(),
xm1,
ym1,
zm1,
};
let out_e = e.into_mut_handle(idx);
let mat = mat_matrix.into_handle(idx);
StepEContext {
inv_feature_size: self.meta.inv_feature_size,
time_step: self.meta.time_step,
stim_e: stim_e_matrix.get(idx).unwrap(),
mat,
in_h,
out_e,
}
}
}
/// Package the field vectors adjacent to some particular location.
/// Particular those at negative offsets from the midpoint.
/// This is used in step_e when looking at the H field deltas.
#[derive(Copy, Clone)]
struct VolumeSampleNeg {
mid: Vec3Std,
xm1: Optional<Vec3Std>,
ym1: Optional<Vec3Std>,
zm1: Optional<Vec3Std>,
}
impl VolumeSampleNeg {
/// Calculate the delta in H values amongst this cell and its neighbors (left/up/out)
fn delta_h(self) -> FieldDeltas {
let mid = self.mid;
// let (dfy_dx, dfz_dx) = self.xm1.map(|xm1| {
// (mid.y() - xm1.y(), mid.z() - xm1.z())
// }).unwrap_or_default();
// let (dfx_dy, dfz_dy) = self.ym1.map(|ym1| {
// (mid.x() - ym1.x(), mid.z() - ym1.z())
// }).unwrap_or_default();
// let (dfx_dz, dfy_dz) = self.zm1.map(|zm1| {
// (mid.x() - zm1.x(), mid.y() - zm1.y())
// }).unwrap_or_default();
let (dfy_dx, dfz_dx) = if self.xm1.is_some() {
(mid.y() - self.xm1.unwrap().y(), mid.z() - self.xm1.unwrap().z())
} else {
(0.0, 0.0)
};
let (dfx_dy, dfz_dy) = if self.ym1.is_some() {
(mid.x() - self.ym1.unwrap().x(), mid.z() - self.ym1.unwrap().z())
} else {
(0.0, 0.0)
};
let (dfx_dz, dfy_dz) = if self.zm1.is_some() {
(mid.x() - self.zm1.unwrap().x(), mid.y() - self.zm1.unwrap().y())
} else {
(0.0, 0.0)
};
FieldDeltas {
dfy_dx,
dfz_dx,
dfx_dy,
dfz_dy,
dfx_dz,
dfy_dz,
}
}
}
/// Package the field vectors adjacent to some particular location.
/// Particular those at positive offsets from the midpoint.
/// This is used in step_h when looking at the E field deltas.
#[derive(Copy, Clone)]
struct VolumeSamplePos {
mid: Vec3Std,
xp1: Optional<Vec3Std>,
yp1: Optional<Vec3Std>,
zp1: Optional<Vec3Std>
}
impl VolumeSamplePos {
/// Calculate the delta in E values amongst this cell and its neighbors (right/down/in)
fn delta_e(self) -> FieldDeltas {
let mid = self.mid;
// let (dfy_dx, dfz_dx) = self.xp1.map(|xp1| {
// (xp1.y() - mid.y(), xp1.z() - mid.z())
// }).unwrap_or_default();
// let (dfx_dy, dfz_dy) = self.yp1.map(|yp1| {
// (yp1.x() - mid.x(), yp1.z() - mid.z())
// }).unwrap_or_default();
// let (dfx_dz, dfy_dz) = self.zp1.map(|zp1| {
// (zp1.x() - mid.x(), zp1.y() - mid.y())
// }).unwrap_or_default();
let (dfy_dx, dfz_dx) = if self.xp1.is_some() {
(self.xp1.unwrap().y() - mid.y(), self.xp1.unwrap().z() - mid.z())
} else {
(0.0, 0.0)
};
let (dfx_dy, dfz_dy) = if self.yp1.is_some() {
(self.yp1.unwrap().x() - mid.x(), self.yp1.unwrap().z() - mid.z())
} else {
(0.0, 0.0)
};
let (dfx_dz, dfy_dz) = if self.zp1.is_some() {
(self.zp1.unwrap().x() - mid.x(), self.zp1.unwrap().y() - mid.y())
} else {
(0.0, 0.0)
};
FieldDeltas {
dfy_dx,
dfz_dx,
dfx_dy,
dfz_dy,
dfx_dz,
dfy_dz,
}
}
}
struct FieldDeltas {
dfy_dx: f32,
dfz_dx: f32,
dfx_dy: f32,
dfz_dy: f32,
dfx_dz: f32,
dfy_dz: f32,
}
impl FieldDeltas {
fn nabla(self) -> Vec3Std {
Vec3Std::new(
self.dfz_dy - self.dfy_dz,
self.dfx_dz - self.dfz_dx,
self.dfy_dx - self.dfx_dy,
)
}
}
pub struct StepEContext<'a, M> {
inv_feature_size: f32,
time_step: f32,
stim_e: Vec3Std,
mat: ArrayHandle<'a, M>,
/// Input field sampled near this location
in_h: VolumeSampleNeg,
/// Handle to the output field at one specific index.
out_e: ArrayHandleMut<'a, Vec3Std>,
}
impl<'a, M: Material> StepEContext<'a, M> {
pub fn step_e(mut self) {
// const EPS0_INV: f32 = 112940906737.1361;
const TWICE_EPS0: f32 = 1.7708375625626e-11;
let deltas = self.in_h.delta_h();
// \nabla x H
let nabla_h = deltas.nabla() * self.inv_feature_size;
// $\nabla x H = \epsilon_0 dE/dt + \sigma E$
// no-conductivity version:
// let delta_e = nabla_h * (self.time_step * EPS0_INV);
let sigma = self.mat.get_ref().conductivity();
let e_prev = self.out_e.get();
let delta_e = (nabla_h - e_prev.elem_mul(sigma)).elem_div(
sigma*self.time_step + Vec3Std::uniform(TWICE_EPS0)
)*(2.0*self.time_step);
// println!("spirv-step_e delta_e: {:?}", delta_e);
self.out_e.write(e_prev + delta_e + self.stim_e);
}
}
pub struct StepHContext<'a, M> {
inv_feature_size: f32,
time_step: f32,
stim_h: Vec3Std,
mat: ArrayHandle<'a, M>,
/// Input field sampled near this location
in_e: VolumeSamplePos,
/// Handle to the output field at one specific index.
out_h: ArrayHandleMut<'a, Vec3Std>,
out_m: ArrayHandleMut<'a, Vec3Std>,
}
impl<'a, M: Material> StepHContext<'a, M> {
pub fn step_h(mut self) {
const MU0: f32 = 1.2566370621219e-06;
const MU0_INV: f32 = 795774.715025073;
let deltas = self.in_e.delta_e();
// println!("spirv-step_h delta_e_struct: {:?}", deltas);
// \nabla x E
let nabla_e = deltas.nabla() * self.inv_feature_size;
// println!("spirv-step_h nabla_e: {:?}", nabla_e);
let delta_b = nabla_e * (-self.time_step);
// Relation between these is: B = mu0*(H + M)
let old_h = self.out_h.get();
let old_m = self.out_m.get();
let old_b = (old_h + old_m) * MU0;
let new_b = old_b + delta_b + self.stim_h * MU0;
let mat = self.mat.get_ref();
let new_m = mat.move_b_vec(old_m, new_b);
let new_h = new_b * MU0_INV - new_m;
// println!("spirv-step_h delta_h: {:?}", delta_h);
self.out_h.write(new_h);
self.out_m.write(new_m);
}
}

View File

@@ -0,0 +1,506 @@
use spirv_std::glam::{self, UVec3};
// pub trait Nullable {
// fn null() -> *const Self;
// fn null_mut() -> *mut Self;
// }
#[derive(Clone, Copy, Default, PartialEq)]
pub struct XYZStd<T>(T, T, T);
pub type Vec3Std = XYZStd<f32>;
pub type UVec3Std = XYZStd<u32>;
impl<T> XYZStd<T> {
pub fn new(x: T, y: T, z: T) -> Self {
Self(x, y, z)
}
}
impl<T: Copy> XYZStd<T> {
pub fn uniform(u: T) -> Self {
Self(u, u, u)
}
pub fn x(self) -> T {
self.0
}
pub fn y(self) -> T {
self.1
}
pub fn z(self) -> T {
self.2
}
}
impl<T: core::ops::Add<T, Output=T>> core::ops::Add<XYZStd<T>> for XYZStd<T> {
type Output = Self;
fn add(self, s: Self) -> Self {
Self::new(self.0 + s.0, self.1 + s.1, self.2 + s.2)
}
}
impl<T: core::ops::Sub<T, Output=T>> core::ops::Sub<XYZStd<T>> for XYZStd<T> {
type Output = Self;
fn sub(self, s: Self) -> Self {
Self::new(self.0 - s.0, self.1 - s.1, self.2 - s.2)
}
}
impl<T: Copy + core::ops::Mul<T, Output=T>> core::ops::Mul<T> for XYZStd<T> {
type Output = Self;
fn mul(self, s: T) -> Self {
Self::new(self.0 * s, self.1 * s, self.2 * s)
}
}
impl Into<glam::Vec3> for Vec3Std {
fn into(self) -> glam::Vec3 {
glam::Vec3::new(self.x(), self.y(), self.z())
}
}
impl Into<glam::UVec3> for UVec3Std {
fn into(self) -> glam::UVec3 {
glam::UVec3::new(self.x(), self.y(), self.z())
}
}
impl<T: core::ops::Mul<T, Output=T>> XYZStd<T> {
pub fn elem_mul(self, other: Self) -> Self {
Self::new(self.0 * other.0, self.1 * other.1, self.2 * other.2)
}
}
impl<T: core::ops::Div<T, Output=T>> XYZStd<T> {
pub fn elem_div(self, other: Self) -> Self {
Self::new(self.0 / other.0, self.1 / other.1, self.2 / other.2)
}
}
// const NULL_VEC3STD: *const Vec3Std = core::ptr::null();
// const NULL_VEC3STD_MUT: *mut Vec3Std = core::ptr::null_mut();
// impl Nullable for Vec3Std {
// fn null() -> *const Self {
// NULL_VEC3STD
// }
// fn null_mut() -> *mut Self {
// NULL_VEC3STD_MUT
// }
// }
// pub struct OptionalRef<'a, T> {
// ptr: *const T, // NULL for None
// _marker: PhantomData<&'a T>,
// }
//
// impl<'a, T> OptionalRef<'a, T> {
// fn from_ptr(ptr: *const T) -> Self {
// Self {
// ptr,
// _marker: PhantomData,
// }
// }
//
// pub fn some(data: &'a T) -> Self {
// Self::from_ptr(data as *const T)
// }
// }
// impl<'a, T: Nullable> OptionalRef<'a, T> {
// pub fn none() -> Self {
// Self::from_ptr(T::null())
// }
//
// pub fn is_some(&self) -> bool {
// self.ptr != T::null()
// }
//
// pub fn unwrap(self) -> &'a T {
// assert!(self.is_some());
// unsafe {
// &*self.ptr
// }
// }
// }
/// This is a spirv-compatible option type.
/// The native rust Option type produces invalid spirv due to its enum nature; this custom option
/// type creates code which will actually compile.
#[derive(Copy, Clone, PartialEq)]
pub struct Optional<T> {
// XXX: not a bool, because: "entrypoint parameter cannot contain a boolean"
present: u8,
data: T,
}
impl<T> Optional<T> {
pub fn some(data: T) -> Self {
Self {
present: 1,
data,
}
}
pub fn explicit_none(data: T) -> Self {
Self {
present: 0,
data,
}
}
pub fn is_some(self) -> bool {
self.present != 0
}
pub fn unwrap(self) -> T {
assert!(self.present != 0);
self.data
}
pub fn map<U: Default, F: FnOnce(T) -> U>(self, f: F) -> Optional<U> {
self.and_then(|inner| Optional::some(f(inner)))
}
pub fn and_then<U: Default, F: FnOnce(T) -> Optional<U>>(self, f: F) -> Optional<U> {
if self.present != 0 {
f(self.unwrap())
} else {
Optional::none()
}
}
pub fn unwrap_or(self, default: T) -> T {
if self.present != 0 {
self.unwrap()
} else {
default
}
}
}
impl<T: Default> Optional<T> {
pub fn none() -> Self {
Self::explicit_none(Default::default())
}
pub fn unwrap_or_default(self) -> T {
self.unwrap_or(Default::default())
}
}
impl<T: Default> Default for Optional<T> {
fn default() -> Self {
Self::none()
}
}
impl<T0: Default, T1: Default> Optional<(T0, T1)> {
pub fn flatten((f0, f1): (Optional<T0>, Optional<T1>)) -> Self {
if f0.present != 0 && f1.present != 0 {
Optional::some((f0.unwrap(), f1.unwrap()))
} else {
Optional::none()
}
}
}
// #[derive(Copy, Clone, Default, PartialEq)]
// pub struct Optional<T>(Option<T>);
//
// impl<T> Optional<T> {
// pub fn some(data: T) -> Self {
// Self(Some(data))
// }
//
// pub fn explicit_none(_data: T) -> Self {
// Self(None)
// }
//
// pub fn is_some(self) -> bool {
// self.0.is_some()
// }
//
// pub fn unwrap(self) -> T {
// self.0.unwrap()
// }
//
// pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Optional<U> {
// Optional(self.0.map(f))
// }
//
// pub fn unwrap_or(self, default: T) -> T {
// self.0.unwrap_or(default)
// }
//
// pub fn none() -> Self {
// Self(None)
// }
// }
//
// impl<T: Default> Optional<T> {
// pub fn unwrap_or_default(self) -> T {
// self.0.unwrap_or_default()
// }
// }
/// This struct allows doing things like *(ptr + offset) = value.
/// Such code wouldn't ordinarily compile with the spirv target because of
/// unsupported pointer math. RuntimeArray exists to overcome this, however
/// it emits invalid code for non-primitive types. Hence, this hack.
// XXX: maximum bytes an array may occupy is 0x7fff_ffff
// We don't know the element size, so assume it to be <= 44 bytes
// pub const MAX_UNSIZED_ARRAY: usize = 0x2e7_ffff;
pub const MAX_UNSIZED_ARRAY: usize = 0x1ff_ffff;
pub struct UnsizedArray<T>(pub [T; MAX_UNSIZED_ARRAY]);
impl<T: Copy> UnsizedArray<T> {
pub unsafe fn index(&self, index: usize) -> T {
// *self.0.index(index)
self.0[index]
// *self.0.get_unchecked(index)
// &self.0
//asm! {
// "%result = OpAccessChain _ {arr} {index}",
// "OpReturnValue %result",
// "%unused = OpLabel",
// arr = in(reg) self,
// index = in(reg) index,
//}
//loop {}
}
pub unsafe fn write(&mut self, index: usize, value: T) {
self.0[index] = value;
}
}
impl<T> UnsizedArray<T> {
pub unsafe fn index_ref(&self, index: usize) -> &T {
&self.0[index]
}
pub unsafe fn get_handle<'a>(&'a self, index: usize) -> ArrayHandle<'a, T> {
ArrayHandle {
base: self,
index,
}
}
pub unsafe fn get_handle_mut<'a>(&'a mut self, index: usize) -> ArrayHandleMut<'a, T> {
ArrayHandleMut {
base: self,
index,
}
}
}
pub struct ArrayHandle<'a, T> {
base: &'a UnsizedArray<T>,
index: usize,
}
impl<'a, T: Copy> ArrayHandle<'a, T> {
pub fn get(&self) -> T {
unsafe {
self.base.index(self.index)
}
}
pub fn get_into(&self, out: &mut T) {
core::clone::Clone::clone_from(out, self.get_ref());
}
}
impl<'a, T> ArrayHandle<'a, T> {
pub fn get_ref(&self) -> &T {
unsafe {
self.base.index_ref(self.index)
}
}
}
pub struct ArrayHandleMut<'a, T> {
base: &'a mut UnsizedArray<T>,
index: usize,
}
impl<'a, T: Copy> ArrayHandleMut<'a, T> {
pub fn write(&mut self, value: T) {
unsafe {
self.base.write(self.index, value);
}
}
pub fn get(&self) -> T {
unsafe {
self.base.index(self.index)
}
}
}
/// 3d dynamically-sized array backed by a borrowed buffer.
#[derive(Clone, Copy)]
pub struct Array3<'a, T> {
data: &'a UnsizedArray<T>,
dim: UVec3,
}
fn index(loc: UVec3, dim: UVec3) -> usize {
((loc.z*dim.y + loc.y)*dim.x + loc.x) as usize
}
fn checked_index(idx: UVec3, dim: UVec3) -> Optional<usize> {
if idx.x < dim.x && idx.y < dim.y && idx.z < dim.z {
let flat_idx = index(idx, dim);
Optional::some(flat_idx)
} else {
Optional::none()
}
}
impl<'a, T> Array3<'a, T> {
pub fn new(data: &'a UnsizedArray<T>, dim: UVec3) -> Self {
Self {
data,
dim,
}
}
pub fn index(self, idx: UVec3) -> Optional<usize> {
checked_index(idx, self.dim)
}
pub fn into_handle(self, idx: UVec3) -> ArrayHandle<'a, T> {
let idx = checked_index(idx, self.dim).unwrap();
unsafe {
self.data.get_handle(idx)
}
}
}
impl<'a, T: Copy + Default> Array3<'a, T> {
pub fn get(&self, idx: UVec3) -> Optional<T> {
let idx = self.index(idx);
if idx.is_some() {
Optional::some(unsafe {
self.data.index(idx.unwrap())
})
} else {
Optional::none()
}
}
}
// impl<'a, T> Array3<'a, T> {
// pub fn get_ref(&self, idx: UVec3) -> OptionalRef<'a, T> {
// OptionalRef::some(unsafe {
// self.data.index(0)
// })
// // OptionalRef::none()
// // let idx = self.index(idx);
// // if idx.is_some() {
// // OptionalRef::some(unsafe {
// // self.data.index(idx.unwrap())
// // })
// // } else {
// // OptionalRef::none()
// // }
// }
// }
// impl<'a> Array3<'a, Vec3Std> {
// pub fn get(&self, idx: UVec3) -> Vec3Std {
// let flat_idx = self.index(idx);
// (self.data[0].0, 0.0, 0.0)
// // let idx = self.index(idx);
// // if idx.is_some() {
// // Optional::some((0.0, 0.0, self.data[idx.unwrap()].0))
// // } else {
// // Optional::none()
// // }
// }
// }
//
/// 3d dynamically-sized array backed by a mutably borrowed buffer.
pub struct Array3Mut<'a, T> {
data: &'a mut UnsizedArray<T>,
dim: UVec3,
}
impl<'a, T> Array3Mut<'a, T> {
pub fn new(data: &'a mut UnsizedArray<T>, dim: UVec3) -> Self {
Self {
data,
dim,
}
}
// fn as_array3<'b>(&'b self) -> Array3<'b, T> {
// Array3::new(self.data, self.dim)
// }
pub fn index(&self, idx: UVec3) -> Optional<usize> {
if idx.x < self.dim.x && idx.y < self.dim.y && idx.z < self.dim.z {
let flat_idx = index(idx, self.dim);
Optional::some(flat_idx)
} else {
Optional::none()
}
}
}
impl<'a, T: Copy> Array3Mut<'a, T> {
pub fn into_mut_handle(self, idx: UVec3) -> ArrayHandleMut<'a, T> {
let idx = self.index(idx).unwrap();
unsafe {
self.data.get_handle_mut(idx)
}
}
// fn index(&self, idx: UVec3) -> Optional<usize> {
// self.as_array3().index(idx)
// }
// // pub fn get(&self, idx: UVec3) -> Option<T> {
// // self.get_ref(idx).cloned()
// // }
// pub fn get_ref<'b>(&'b self, idx: UVec3) -> OptionalRef<'b, T> {
// let idx = self.index(idx);
// if idx.is_some() {
// OptionalRef::some(&self.data[idx.unwrap()])
// } else {
// OptionalRef::none()
// }
// }
// pub fn get_mut(&mut self, idx: UVec3) -> Option<T> {
// self.get_ref_mut(idx).cloned()
// }
// pub fn get_ref_mut<'b>(&'b mut self, idx: UVec3) -> Option<&'b mut T> {
// self.data.get_mut(index(idx, self.dim))
// }
// /// Like `get_ref_mut`, but imposes fewer lifetime requirements by consuming `self`.
// pub fn into_ref_mut(self, idx: UVec3) -> Option<&'a mut T> {
// self.data.get_mut(index(idx, self.dim))
// }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_index() {
let dim = UVec3::new(2, 3, 7);
assert_eq!(index(UVec3::new(0, 0, 0), dim), 0);
assert_eq!(index(UVec3::new(1, 0, 0), dim), 1);
assert_eq!(index(UVec3::new(0, 1, 0), dim), 2);
assert_eq!(index(UVec3::new(1, 1, 0), dim), 3);
assert_eq!(index(UVec3::new(0, 2, 0), dim), 4);
assert_eq!(index(UVec3::new(0, 0, 1), dim), 6);
assert_eq!(index(UVec3::new(1, 0, 1), dim), 7);
assert_eq!(index(UVec3::new(0, 1, 1), dim), 8);
assert_eq!(index(UVec3::new(1, 2, 1), dim), 11);
assert_eq!(index(UVec3::new(1, 2, 2), dim), 17);
}
}