use glam::DVec3;
#[cfg(target_arch = "x86_64")]
use super::SimdLevel;
use super::simd_level;
pub const TRIPLE_LANES: usize = 8;
#[derive(Default, Clone)]
pub struct TripleBatch {
pub ax: [f64; TRIPLE_LANES],
pub ay: [f64; TRIPLE_LANES],
pub az: [f64; TRIPLE_LANES],
pub bx: [f64; TRIPLE_LANES],
pub by: [f64; TRIPLE_LANES],
pub bz: [f64; TRIPLE_LANES],
pub cx: [f64; TRIPLE_LANES],
pub cy: [f64; TRIPLE_LANES],
pub cz: [f64; TRIPLE_LANES],
pub ma: [f64; TRIPLE_LANES],
pub mb: [f64; TRIPLE_LANES],
pub mc: [f64; TRIPLE_LANES],
pub len: usize,
}
impl TripleBatch {
pub const fn push(&mut self, a: (DVec3, f64), b: (DVec3, f64), c: (DVec3, f64)) -> bool {
let i = self.len;
self.ax[i] = a.0.x;
self.ay[i] = a.0.y;
self.az[i] = a.0.z;
self.bx[i] = b.0.x;
self.by[i] = b.0.y;
self.bz[i] = b.0.z;
self.cx[i] = c.0.x;
self.cy[i] = c.0.y;
self.cz[i] = c.0.z;
self.ma[i] = a.1;
self.mb[i] = b.1;
self.mc[i] = c.1;
self.len = i + 1;
self.len == TRIPLE_LANES
}
}
pub struct TripleSolution {
pub det: [f64; TRIPLE_LANES],
pub vx: [f64; TRIPLE_LANES],
pub vy: [f64; TRIPLE_LANES],
pub vz: [f64; TRIPLE_LANES],
}
fn solve_triple_lane_reference(a: DVec3, b: DVec3, c: DVec3, rhs: DVec3) -> (f64, DVec3) {
use glam::DMat3;
let m = DMat3::from_cols(a, b, c).transpose();
let det = m.determinant();
(det, m.inverse() * rhs)
}
#[cfg(target_arch = "x86_64")]
macro_rules! triple_lane_program {
($t:ty, $mul:expr, $add:expr, $sub:expr, $div1:expr,
$ax:expr, $ay:expr, $az:expr, $bx:expr, $by:expr, $bz:expr,
$cx:expr, $cy:expr, $cz:expr, $ma:expr, $mb:expr, $mc:expr) => {{
let mul = $mul;
let add = $add;
let sub = $sub;
let div1 = $div1;
let t2x = sub(mul($bx, $cy), mul($by, $cx));
let t2y = sub(mul($cx, $ay), mul($cy, $ax));
let t2z = sub(mul($ax, $by), mul($ay, $bx));
let det = add(add(mul($az, t2x), mul($bz, t2y)), mul($cz, t2z));
let t0x = sub(mul($by, $cz), mul($bz, $cy));
let t0y = sub(mul($cy, $az), mul($cz, $ay));
let t0z = sub(mul($ay, $bz), mul($az, $by));
let t1x = sub(mul($bz, $cx), mul($bx, $cz));
let t1y = sub(mul($cz, $ax), mul($cx, $az));
let t1z = sub(mul($az, $bx), mul($ax, $bz));
let inv_det = div1(det);
let vx = add(
add(mul(mul(t0x, inv_det), $ma), mul(mul(t0y, inv_det), $mb)),
mul(mul(t0z, inv_det), $mc),
);
let vy = add(
add(mul(mul(t1x, inv_det), $ma), mul(mul(t1y, inv_det), $mb)),
mul(mul(t1z, inv_det), $mc),
);
let vz = add(
add(mul(mul(t2x, inv_det), $ma), mul(mul(t2y, inv_det), $mb)),
mul(mul(t2z, inv_det), $mc),
);
(det, vx, vy, vz)
}};
}
fn solve_triple_batch_scalar(batch: &TripleBatch) -> TripleSolution {
let mut out = TripleSolution {
det: [0.0; TRIPLE_LANES],
vx: [0.0; TRIPLE_LANES],
vy: [0.0; TRIPLE_LANES],
vz: [0.0; TRIPLE_LANES],
};
for i in 0..batch.len {
let (det, v) = solve_triple_lane_reference(
DVec3::new(batch.ax[i], batch.ay[i], batch.az[i]),
DVec3::new(batch.bx[i], batch.by[i], batch.bz[i]),
DVec3::new(batch.cx[i], batch.cy[i], batch.cz[i]),
DVec3::new(batch.ma[i], batch.mb[i], batch.mc[i]),
);
out.det[i] = det;
out.vx[i] = v.x;
out.vy[i] = v.y;
out.vz[i] = v.z;
}
out
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
fn solve_triple_batch_avx2(batch: &TripleBatch) -> TripleSolution {
use std::arch::x86_64::{
_mm256_add_pd, _mm256_div_pd, _mm256_loadu_pd, _mm256_mul_pd, _mm256_set1_pd,
_mm256_storeu_pd, _mm256_sub_pd,
};
let mut out = TripleSolution {
det: [0.0; TRIPLE_LANES],
vx: [0.0; TRIPLE_LANES],
vy: [0.0; TRIPLE_LANES],
vz: [0.0; TRIPLE_LANES],
};
for half in 0..2usize {
let o = half * 4;
if o >= batch.len {
break;
}
unsafe {
let ld = |arr: &[f64; TRIPLE_LANES]| _mm256_loadu_pd(arr.as_ptr().add(o));
let (ax, ay, az) = (ld(&batch.ax), ld(&batch.ay), ld(&batch.az));
let (bx, by, bz) = (ld(&batch.bx), ld(&batch.by), ld(&batch.bz));
let (cx, cy, cz) = (ld(&batch.cx), ld(&batch.cy), ld(&batch.cz));
let (ma, mb, mc) = (ld(&batch.ma), ld(&batch.mb), ld(&batch.mc));
let one = _mm256_set1_pd(1.0);
let (det, vx, vy, vz) = triple_lane_program!(
__m256d,
|a, b| _mm256_mul_pd(a, b),
|a, b| _mm256_add_pd(a, b),
|a, b| _mm256_sub_pd(a, b),
|d| _mm256_div_pd(one, d),
ax,
ay,
az,
bx,
by,
bz,
cx,
cy,
cz,
ma,
mb,
mc
);
_mm256_storeu_pd(out.det.as_mut_ptr().add(o), det);
_mm256_storeu_pd(out.vx.as_mut_ptr().add(o), vx);
_mm256_storeu_pd(out.vy.as_mut_ptr().add(o), vy);
_mm256_storeu_pd(out.vz.as_mut_ptr().add(o), vz);
}
}
out
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx512f")]
fn solve_triple_batch_avx512(batch: &TripleBatch) -> TripleSolution {
use std::arch::x86_64::{
_mm512_add_pd, _mm512_div_pd, _mm512_loadu_pd, _mm512_mul_pd, _mm512_set1_pd,
_mm512_storeu_pd, _mm512_sub_pd,
};
let mut out = TripleSolution {
det: [0.0; TRIPLE_LANES],
vx: [0.0; TRIPLE_LANES],
vy: [0.0; TRIPLE_LANES],
vz: [0.0; TRIPLE_LANES],
};
unsafe {
let ld = |arr: &[f64; TRIPLE_LANES]| _mm512_loadu_pd(arr.as_ptr());
let (ax, ay, az) = (ld(&batch.ax), ld(&batch.ay), ld(&batch.az));
let (bx, by, bz) = (ld(&batch.bx), ld(&batch.by), ld(&batch.bz));
let (cx, cy, cz) = (ld(&batch.cx), ld(&batch.cy), ld(&batch.cz));
let (ma, mb, mc) = (ld(&batch.ma), ld(&batch.mb), ld(&batch.mc));
let one = _mm512_set1_pd(1.0);
let (det, vx, vy, vz) = triple_lane_program!(
__m512d,
|a, b| _mm512_mul_pd(a, b),
|a, b| _mm512_add_pd(a, b),
|a, b| _mm512_sub_pd(a, b),
|d| _mm512_div_pd(one, d),
ax,
ay,
az,
bx,
by,
bz,
cx,
cy,
cz,
ma,
mb,
mc
);
_mm512_storeu_pd(out.det.as_mut_ptr(), det);
_mm512_storeu_pd(out.vx.as_mut_ptr(), vx);
_mm512_storeu_pd(out.vy.as_mut_ptr(), vy);
_mm512_storeu_pd(out.vz.as_mut_ptr(), vz);
}
out
}
#[must_use]
pub fn solve_triple_batch(batch: &TripleBatch) -> TripleSolution {
match simd_level() {
#[cfg(target_arch = "x86_64")]
SimdLevel::Avx512 => unsafe { solve_triple_batch_avx512(batch) },
#[cfg(target_arch = "x86_64")]
SimdLevel::Avx2 => unsafe { solve_triple_batch_avx2(batch) },
_ => solve_triple_batch_scalar(batch),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn triple_solve_matches_glam_bitwise() {
let mut state = 42u64;
let mut next = move || {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1);
(((state >> 33) as f64) / f64::from(u32::MAX)).mul_add(2.0, -1.0)
};
for _round in 0..50 {
let mut batch = TripleBatch::default();
let mut refs: Vec<(f64, DVec3)> = Vec::new();
for _lane in 0..TRIPLE_LANES {
let a = (DVec3::new(next(), next(), next()).normalize(), next());
let b = (DVec3::new(next(), next(), next()).normalize(), next());
let c = (DVec3::new(next(), next(), next()).normalize(), next());
refs.push(solve_triple_lane_reference(
a.0,
b.0,
c.0,
DVec3::new(a.1, b.1, c.1),
));
batch.push(a, b, c);
}
let check = |sol: &TripleSolution| {
for (lane, (det, v)) in refs.iter().enumerate() {
assert_eq!(sol.det[lane].to_bits(), det.to_bits(), "det lane {lane}");
assert_eq!(sol.vx[lane].to_bits(), v.x.to_bits(), "vx lane {lane}");
assert_eq!(sol.vy[lane].to_bits(), v.y.to_bits(), "vy lane {lane}");
assert_eq!(sol.vz[lane].to_bits(), v.z.to_bits(), "vz lane {lane}");
}
};
check(&solve_triple_batch_scalar(&batch));
check(&solve_triple_batch(&batch));
#[cfg(target_arch = "x86_64")]
{
if std::arch::is_x86_feature_detected!("avx2") {
unsafe { check(&solve_triple_batch_avx2(&batch)) };
}
if std::arch::is_x86_feature_detected!("avx512f") {
unsafe { check(&solve_triple_batch_avx512(&batch)) };
}
}
}
}
}