1use std::ops::{Add, Div, Mul, Neg, Sub};
7
8#[repr(C, align(16))]
12#[derive(Debug, Clone, Copy, PartialEq)]
13#[cfg_attr(feature = "vulkan", derive(bytemuck::Pod, bytemuck::Zeroable))]
14pub struct StaVector {
15 pub ct: f32, pub x: f32, pub y: f32, pub z: f32, }
20
21impl Default for StaVector {
22 #[inline(always)]
23 fn default() -> Self {
24 Self::zero()
25 }
26}
27
28impl StaVector {
29 #[inline(always)]
30 pub const fn new(ct: f32, x: f32, y: f32, z: f32) -> Self {
31 Self { ct, x, y, z }
32 }
33
34 #[inline(always)]
35 pub const fn zero() -> Self {
36 Self { ct: 0.0, x: 0.0, y: 0.0, z: 0.0 }
37 }
38
39 #[inline(always)]
41 pub fn interval_sq(&self) -> f32 {
42 self.ct * self.ct - (self.x * self.x + self.y * self.y + self.z * self.z)
43 }
44
45 #[inline(always)]
47 pub fn dot(&self, other: &Self) -> f32 {
48 self.ct * other.ct - (self.x * other.x + self.y * other.y + self.z * other.z)
49 }
50
51 #[inline(always)]
53 pub fn is_timelike(&self) -> bool {
54 self.interval_sq() > 0.0
55 }
56
57 #[inline(always)]
59 pub fn is_spacelike(&self) -> bool {
60 self.interval_sq() < 0.0
61 }
62
63 #[inline(always)]
65 pub fn is_lightlike(&self) -> bool {
66 self.interval_sq().abs() < 1e-12
67 }
68
69 #[inline]
71 pub fn proper_time(&self) -> Option<f32> {
72 let s2 = self.interval_sq();
73 if s2 >= 0.0 {
74 Some(s2.sqrt())
75 } else {
76 None
77 }
78 }
79}
80
81impl Add for StaVector {
82 type Output = Self;
83 #[inline(always)]
84 fn add(self, rhs: Self) -> Self {
85 Self {
86 ct: self.ct + rhs.ct,
87 x: self.x + rhs.x,
88 y: self.y + rhs.y,
89 z: self.z + rhs.z,
90 }
91 }
92}
93
94impl Sub for StaVector {
95 type Output = Self;
96 #[inline(always)]
97 fn sub(self, rhs: Self) -> Self {
98 Self {
99 ct: self.ct - rhs.ct,
100 x: self.x - rhs.x,
101 y: self.y - rhs.y,
102 z: self.z - rhs.z,
103 }
104 }
105}
106
107impl Neg for StaVector {
108 type Output = Self;
109 #[inline(always)]
110 fn neg(self) -> Self {
111 Self {
112 ct: -self.ct,
113 x: -self.x,
114 y: -self.y,
115 z: -self.z,
116 }
117 }
118}
119
120impl Mul<f32> for StaVector {
121 type Output = Self;
122 #[inline(always)]
123 fn mul(self, rhs: f32) -> Self {
124 Self {
125 ct: self.ct * rhs,
126 x: self.x * rhs,
127 y: self.y * rhs,
128 z: self.z * rhs,
129 }
130 }
131}
132
133impl Div<f32> for StaVector {
134 type Output = Self;
135 #[inline(always)]
136 fn div(self, rhs: f32) -> Self {
137 let inv = 1.0 / rhs;
138 Self {
139 ct: self.ct * inv,
140 x: self.x * inv,
141 y: self.y * inv,
142 z: self.z * inv,
143 }
144 }
145}
146
147#[repr(C, align(32))]
150#[derive(Debug, Clone, Copy, PartialEq)]
151#[cfg_attr(feature = "vulkan", derive(bytemuck::Pod, bytemuck::Zeroable))]
152pub struct StaBivector {
153 pub ex: f32, pub ey: f32, pub ez: f32, pub bx: f32, pub by: f32, pub bz: f32, pub _pad0: f32,
160 pub _pad1: f32,
161}
162
163impl Default for StaBivector {
164 #[inline(always)]
165 fn default() -> Self {
166 Self::zero()
167 }
168}
169
170impl StaBivector {
171 #[inline(always)]
172 pub const fn new(ex: f32, ey: f32, ez: f32, bx: f32, by: f32, bz: f32) -> Self {
173 Self {
174 ex,
175 ey,
176 ez,
177 bx,
178 by,
179 bz,
180 _pad0: 0.0,
181 _pad1: 0.0,
182 }
183 }
184
185 #[inline(always)]
186 pub const fn zero() -> Self {
187 Self::new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
188 }
189
190 #[inline(always)]
192 pub fn invariant_1(&self) -> f32 {
193 (self.ex * self.ex + self.ey * self.ey + self.ez * self.ez)
194 - (self.bx * self.bx + self.by * self.by + self.bz * self.bz)
195 }
196
197 #[inline(always)]
199 pub fn invariant_2(&self) -> f32 {
200 2.0 * (self.ex * self.bx + self.ey * self.by + self.ez * self.bz)
201 }
202
203 #[inline]
205 pub fn poynting_vector(&self) -> [f32; 3] {
206 [
207 self.ey * self.bz - self.ez * self.by,
208 self.ez * self.bx - self.ex * self.bz,
209 self.ex * self.by - self.ey * self.bx,
210 ]
211 }
212}
213
214#[repr(C, align(16))]
217#[derive(Debug, Clone, Copy, PartialEq)]
218#[cfg_attr(feature = "vulkan", derive(bytemuck::Pod, bytemuck::Zeroable))]
219pub struct StaLorentzBoost {
220 pub cosh_half: f32,
221 pub sinh_half_x: f32,
222 pub sinh_half_y: f32,
223 pub sinh_half_z: f32,
224}
225
226impl StaLorentzBoost {
227 #[inline]
230 pub fn from_velocity(vx: f32, vy: f32, vz: f32) -> Self {
231 let v2 = vx * vx + vy * vy + vz * vz;
232 let beta = v2.sqrt();
233 assert!(beta < 1.0, "Velocity must be strictly subluminal (|v| < c)");
234
235 if beta < 1e-12 {
236 return Self {
237 cosh_half: 1.0,
238 sinh_half_x: 0.0,
239 sinh_half_y: 0.0,
240 sinh_half_z: 0.0,
241 };
242 }
243
244 let gamma = 1.0 / (1.0 - beta * beta).sqrt();
246 let cosh_half = ((gamma + 1.0) * 0.5).sqrt();
250 let sinh_half = ((gamma - 1.0) * 0.5).sqrt();
251
252 let inv_beta = 1.0 / beta;
253 Self {
254 cosh_half,
255 sinh_half_x: sinh_half * (vx * inv_beta),
256 sinh_half_y: sinh_half * (vy * inv_beta),
257 sinh_half_z: sinh_half * (vz * inv_beta),
258 }
259 }
260
261 #[inline]
263 pub fn transform_vector(&self, v: &StaVector) -> StaVector {
264 let ch = self.cosh_half;
265 let sx = self.sinh_half_x;
266 let sy = self.sinh_half_y;
267 let sz = self.sinh_half_z;
268
269 let s2 = sx * sx + sy * sy + sz * sz;
270 let gamma = ch * ch + s2;
271 let two_ch = 2.0 * ch;
272
273 let s_dot_r = sx * v.x + sy * v.y + sz * v.z;
275 let new_ct = gamma * v.ct - two_ch * s_dot_r;
276
277 let factor = if s2 > 1e-12 {
278 (gamma - 1.0) * s_dot_r / s2
279 } else {
280 0.0
281 };
282
283 let new_x = v.x - two_ch * v.ct * sx + factor * sx;
284 let new_y = v.y - two_ch * v.ct * sy + factor * sy;
285 let new_z = v.z - two_ch * v.ct * sz + factor * sz;
286
287 StaVector::new(new_ct, new_x, new_y, new_z)
288 }
289}
290
291#[derive(Debug, Clone, Copy, PartialEq, Eq)]
294pub enum LounestoClass {
295 Class1DiracGeneral,
297 Class2DiracElectron,
299 Class3DiracPseudoscalar,
301 Class4FlagDipole,
303 Class5MajoranaFlagPole,
305 Class6WeylDipole,
307}
308
309#[derive(Debug, Clone, Copy, PartialEq)]
312pub struct SpinorBilinearCovariants {
313 pub sigma: f32, pub omega: f32, pub j: StaVector, pub s: StaBivector, pub k: StaVector, }
319
320#[repr(C, align(32))]
324#[derive(Debug, Clone, Copy, PartialEq)]
325#[cfg_attr(feature = "vulkan", derive(bytemuck::Pod, bytemuck::Zeroable))]
326pub struct StaSpinor {
327 pub scalar: f32, pub ex: f32, pub ey: f32, pub ez: f32, pub bx: f32, pub by: f32, pub bz: f32, pub pseudoscalar: f32, }
336
337impl Default for StaSpinor {
338 #[inline(always)]
339 fn default() -> Self {
340 Self::zero()
341 }
342}
343
344impl StaSpinor {
345 #[inline(always)]
346 pub const fn zero() -> Self {
347 Self {
348 scalar: 0.0,
349 ex: 0.0,
350 ey: 0.0,
351 ez: 0.0,
352 bx: 0.0,
353 by: 0.0,
354 bz: 0.0,
355 pseudoscalar: 0.0,
356 }
357 }
358
359 #[inline(always)]
360 pub const fn new(
361 scalar: f32,
362 ex: f32,
363 ey: f32,
364 ez: f32,
365 bx: f32,
366 by: f32,
367 bz: f32,
368 pseudoscalar: f32,
369 ) -> Self {
370 Self {
371 scalar,
372 ex,
373 ey,
374 ez,
375 bx,
376 by,
377 bz,
378 pseudoscalar,
379 }
380 }
381
382 #[inline(always)]
384 pub fn reverse(&self) -> Self {
385 Self {
386 scalar: self.scalar,
387 ex: -self.ex,
388 ey: -self.ey,
389 ez: -self.ez,
390 bx: -self.bx,
391 by: -self.by,
392 bz: -self.bz,
393 pseudoscalar: self.pseudoscalar,
394 }
395 }
396
397 #[inline]
399 pub fn bilinear_covariants(&self) -> SpinorBilinearCovariants {
400 let s = self.scalar;
401 let p = self.pseudoscalar;
402 let ex = self.ex;
403 let ey = self.ey;
404 let ez = self.ez;
405 let bx = self.bx;
406 let by = self.by;
407 let bz = self.bz;
408
409 let sigma = (s * s - p * p) - (ex * ex + ey * ey + ez * ez) + (bx * bx + by * by + bz * bz);
411
412 let omega = 2.0 * (s * p - (ex * bx + ey * by + ez * bz));
414
415 let j0 = s * s + p * p + (ex * ex + ey * ey + ez * ez) + (bx * bx + by * by + bz * bz);
417 let jx = 2.0 * (s * ex + p * bx + ey * bz - ez * by);
418 let jy = 2.0 * (s * ey + p * by + ez * bx - ex * bz);
419 let jz = 2.0 * (s * ez + p * bz + ex * by - ey * bx);
420 let j = StaVector::new(j0, jx, jy, jz);
421
422 let sex = 2.0 * (s * bx - p * ex + ey * ez);
424 let sey = 2.0 * (s * by - p * ey + ez * ex);
425 let sez = 2.0 * (s * bz - p * ez + ex * ey);
426 let sbx = s * s - p * p + ex * ex - ey * ey - ez * ez - bx * bx + by * by + bz * bz;
427 let sby = 2.0 * (ex * ey - bx * by);
428 let sbz = 2.0 * (ex * ez - bx * bz);
429 let s_biv = StaBivector::new(sex, sey, sez, sbx, sby, sbz);
430
431 let k0 = 2.0 * (s * p + ex * bx + ey * by + ez * bz);
433 let kx = 2.0 * (p * ex - s * bx + ey * bz - ez * by);
434 let ky = 2.0 * (p * ey - s * by + ez * bx - ex * bz);
435 let kz = 2.0 * (p * ez - s * bz + ex * by - ey * bx);
436 let k = StaVector::new(k0, kx, ky, kz);
437
438 SpinorBilinearCovariants {
439 sigma,
440 omega,
441 j,
442 s: s_biv,
443 k,
444 }
445 }
446
447 #[inline]
449 pub fn lounesto_class(&self, tol: f32) -> LounestoClass {
450 let cov = self.bilinear_covariants();
451 let sig_nonzero = cov.sigma.abs() > tol;
452 let ome_nonzero = cov.omega.abs() > tol;
453
454 if sig_nonzero && ome_nonzero {
455 LounestoClass::Class1DiracGeneral
456 } else if sig_nonzero && !ome_nonzero {
457 LounestoClass::Class2DiracElectron
458 } else if !sig_nonzero && ome_nonzero {
459 LounestoClass::Class3DiracPseudoscalar
460 } else {
461 let s_norm_sq = cov.s.invariant_1().abs() + cov.s.invariant_2().abs();
463 let k_norm_sq = cov.k.interval_sq().abs();
464 let s_nonzero = s_norm_sq > tol * tol;
465 let k_nonzero = k_norm_sq > tol * tol;
466
467 if s_nonzero && k_nonzero {
468 LounestoClass::Class4FlagDipole
469 } else if s_nonzero && !k_nonzero {
470 LounestoClass::Class5MajoranaFlagPole
471 } else {
472 LounestoClass::Class6WeylDipole
473 }
474 }
475 }
476}