use crate::poincare::cayley_klein::CayleyKleinSpace;
use crate::lattice::eisenstein::{
RATIO_2_SQRT3, SQRT3_DIV2_INV,
};
use crate::pga::{PgaMotor, PgaPoint};
#[repr(C, align(64))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Universal64Accumulator {
pub lanes: [f32; 16],
}
impl Default for Universal64Accumulator {
#[inline(always)]
fn default() -> Self {
Self::zero()
}
}
impl Universal64Accumulator {
#[inline(always)]
pub const fn zero() -> Self {
Self { lanes: [0.0f32; 16] }
}
#[inline(always)]
pub const fn from_lanes(lanes: [f32; 16]) -> Self {
Self { lanes }
}
#[inline(always)]
pub fn horizontal_sum(&self) -> f32 {
let s0 = (self.lanes[0] + self.lanes[1]) + (self.lanes[2] + self.lanes[3]);
let s1 = (self.lanes[4] + self.lanes[5]) + (self.lanes[6] + self.lanes[7]);
let s2 = (self.lanes[8] + self.lanes[9]) + (self.lanes[10] + self.lanes[11]);
let s3 = (self.lanes[12] + self.lanes[13]) + (self.lanes[14] + self.lanes[15]);
(s0 + s1) + (s2 + s3)
}
}
#[repr(C, align(64))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Universal64Chunk {
pub words: [u64; 8],
}
impl Default for Universal64Chunk {
#[inline(always)]
fn default() -> Self {
Self { words: [0u64; 8] }
}
}
#[repr(C, align(64))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SoABatch16 {
pub x: [f32; 16],
pub y: [f32; 16],
pub z: [f32; 16],
pub w: [f32; 16],
}
impl Default for SoABatch16 {
#[inline(always)]
fn default() -> Self {
Self {
x: [0.0; 16],
y: [0.0; 16],
z: [0.0; 16],
w: [0.0; 16],
}
}
}
impl SoABatch16 {
pub const CAPACITY: usize = 16;
#[inline(always)]
pub fn zero() -> Self {
Self::default()
}
pub fn from_aos_3d(points: &[[f32; 3]; 16]) -> Self {
let mut batch = Self::default();
for i in 0..16 {
batch.x[i] = points[i][0];
batch.y[i] = points[i][1];
batch.z[i] = points[i][2];
batch.w[i] = 1.0;
}
batch
}
pub fn batch_geodesic_distance(
&self,
other: &Self,
space: &CayleyKleinSpace,
) -> Universal64Accumulator {
let mut out = Universal64Accumulator::zero();
let tau = space.tau;
if tau.abs() < 1e-7 {
for i in 0..16 {
let dx = self.x[i] - other.x[i];
let dy = self.y[i] - other.y[i];
let dz = self.z[i] - other.z[i];
out.lanes[i] = (dx * dx + dy * dy + dz * dz).sqrt();
}
return out;
}
if tau < 0.0 {
let abs_tau = -tau;
let inv_sqrt_tau = 1.0f32 / abs_tau.sqrt();
for i in 0..16 {
let dx = self.x[i] - other.x[i];
let dy = self.y[i] - other.y[i];
let dz = self.z[i] - other.z[i];
let diff_sq = dx * dx + dy * dy + dz * dz;
let u_sq = self.x[i] * self.x[i] + self.y[i] * self.y[i] + self.z[i] * self.z[i];
let v_sq = other.x[i] * other.x[i] + other.y[i] * other.y[i] + other.z[i] * other.z[i];
let u_fact = (1.0f32 - abs_tau * u_sq).max(1e-7);
let v_fact = (1.0f32 - abs_tau * v_sq).max(1e-7);
let denom = u_fact * v_fact;
let delta = (1.0f32 + (2.0f32 * abs_tau * diff_sq) / denom).max(1.0f32);
let arcosh = (delta + (delta * delta - 1.0f32).sqrt()).ln();
out.lanes[i] = inv_sqrt_tau * arcosh;
}
} else {
let inv_sqrt_tau = 1.0f32 / tau.sqrt();
for i in 0..16 {
let dx = self.x[i] - other.x[i];
let dy = self.y[i] - other.y[i];
let dz = self.z[i] - other.z[i];
let diff_sq = dx * dx + dy * dy + dz * dz;
let u_sq = self.x[i] * self.x[i] + self.y[i] * self.y[i] + self.z[i] * self.z[i];
let v_sq = other.x[i] * other.x[i] + other.y[i] * other.y[i] + other.z[i] * other.z[i];
let u_fact = 1.0f32 + tau * u_sq;
let v_fact = 1.0f32 + tau * v_sq;
let denom = (u_fact * v_fact).max(1e-7);
let arg = (1.0f32 - (2.0f32 * tau * diff_sq) / denom).clamp(-1.0f32, 1.0f32);
out.lanes[i] = inv_sqrt_tau * arg.acos();
}
}
out
}
pub fn batch_project_to_euclidean(&mut self, space: &CayleyKleinSpace) {
let tau = space.tau;
if tau.abs() < 1e-7 {
return;
}
let tau_div_4 = tau * 0.25f32;
for i in 0..16 {
let norm_sq = self.x[i] * self.x[i] + self.y[i] * self.y[i] + self.z[i] * self.z[i];
let denom = 1.0f32 + tau_div_4 * norm_sq;
let scale = 1.0f32 / denom.max(1e-7);
self.x[i] *= scale;
self.y[i] *= scale;
self.z[i] *= scale;
}
}
pub fn batch_to_eisenstein_a3(&self) -> ([i32; 16], [i32; 16], [i32; 16]) {
let mut u_out = [0i32; 16];
let mut v_out = [0i32; 16];
let mut w_out = [0i32; 16];
for i in 0..16 {
let w_f = self.z[i] * SQRT3_DIV2_INV;
let w = w_f.round() as i32;
let v_f = self.y[i] * RATIO_2_SQRT3 + (w as f32) / 3.0f32;
let v = v_f.round() as i32;
let u_f = self.x[i] + 0.5f32 * (v as f32) + 0.5f32 * (w as f32);
let u = u_f.round() as i32;
u_out[i] = u;
v_out[i] = v;
w_out[i] = w;
}
(u_out, v_out, w_out)
}
#[inline]
pub fn batch_planar_spinor_rotate(&mut self, cos_th: f32, sin_th: f32) {
for i in 0..16 {
let u = self.x[i];
let v = self.y[i];
self.x[i] = u * cos_th - v * sin_th;
self.y[i] = u * sin_th + v * cos_th;
}
}
}
#[repr(C, align(64))]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "vulkan", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct SoAPgaMotorBatch16 {
pub s: [f32; 16], pub b23: [f32; 16], pub b31: [f32; 16], pub b12: [f32; 16], pub d01: [f32; 16], pub d02: [f32; 16], pub d03: [f32; 16], pub e0123: [f32; 16], }
impl Default for SoAPgaMotorBatch16 {
#[inline(always)]
fn default() -> Self {
Self::identity()
}
}
impl SoAPgaMotorBatch16 {
#[inline]
pub fn identity() -> Self {
Self {
s: [1.0f32; 16],
b23: [0.0f32; 16],
b31: [0.0f32; 16],
b12: [0.0f32; 16],
d01: [0.0f32; 16],
d02: [0.0f32; 16],
d03: [0.0f32; 16],
e0123: [0.0f32; 16],
}
}
#[inline]
pub fn broadcast(m: &PgaMotor) -> Self {
Self {
s: [m.s; 16],
b23: [m.b23; 16],
b31: [m.b31; 16],
b12: [m.b12; 16],
d01: [m.d01; 16],
d02: [m.d02; 16],
d03: [m.d03; 16],
e0123: [m.e0123; 16],
}
}
#[inline]
pub fn from_aos(motors: &[PgaMotor; 16]) -> Self {
let mut batch = Self::identity();
for i in 0..16 {
batch.s[i] = motors[i].s;
batch.b23[i] = motors[i].b23;
batch.b31[i] = motors[i].b31;
batch.b12[i] = motors[i].b12;
batch.d01[i] = motors[i].d01;
batch.d02[i] = motors[i].d02;
batch.d03[i] = motors[i].d03;
batch.e0123[i] = motors[i].e0123;
}
batch
}
#[inline]
pub fn get_motor(&self, i: usize) -> PgaMotor {
assert!(i < 16, "Index out of range for 16-lane motor batch");
PgaMotor::new(
self.s[i],
self.b23[i],
self.b31[i],
self.b12[i],
self.d01[i],
self.d02[i],
self.d03[i],
self.e0123[i],
)
}
#[inline]
pub fn transform_points(&self, points: &mut SoABatch16) {
for i in 0..16 {
let m = self.get_motor(i);
let pt = PgaPoint::new(points.x[i], points.y[i], points.z[i]);
let t_pt = m.transform_point(&pt);
let [x, y, z] = t_pt.to_euclidean();
points.x[i] = x;
points.y[i] = y;
points.z[i] = z;
}
}
}