1pub mod eigen;
29pub mod linalg;
30pub mod matrix_exp;
31
32#[cfg(feature = "gpu-compute")]
33pub mod gpu_linalg;
34
35#[cfg(feature = "gpu-compute")]
36pub mod gpu_training;
37
38#[derive(Debug, Clone, Copy, Default, PartialEq)]
48#[repr(C)]
49pub struct Complex {
50 pub re: f32,
51 pub im: f32,
52}
53
54#[cfg(feature = "gpu-compute")]
56unsafe impl bytemuck::Pod for Complex {}
57#[cfg(feature = "gpu-compute")]
58unsafe impl bytemuck::Zeroable for Complex {}
59
60impl Complex {
61 pub const ZERO: Self = Self { re: 0.0, im: 0.0 };
62 pub const ONE: Self = Self { re: 1.0, im: 0.0 };
63 pub const I: Self = Self { re: 0.0, im: 1.0 };
64
65 #[inline]
66 pub fn new(re: f32, im: f32) -> Self {
67 Self { re, im }
68 }
69 #[inline]
70 pub fn from_real(r: f32) -> Self {
71 Self { re: r, im: 0.0 }
72 }
73 #[inline]
74 pub fn from_imag(i: f32) -> Self {
75 Self { re: 0.0, im: i }
76 }
77
78 #[inline]
80 pub fn conj(self) -> Self {
81 Self {
82 re: self.re,
83 im: -self.im,
84 }
85 }
86
87 #[inline]
89 pub fn norm_sq(self) -> f32 {
90 self.re * self.re + self.im * self.im
91 }
92
93 #[inline]
95 pub fn norm(self) -> f32 {
96 self.norm_sq().sqrt()
97 }
98
99 #[inline]
101 pub fn mul(self, other: Self) -> Self {
102 Self {
103 re: self.re * other.re - self.im * other.im,
104 im: self.re * other.im + self.im * other.re,
105 }
106 }
107
108 #[inline]
109 pub fn add(self, other: Self) -> Self {
110 Self {
111 re: self.re + other.re,
112 im: self.im + other.im,
113 }
114 }
115
116 #[inline]
117 pub fn sub(self, other: Self) -> Self {
118 Self {
119 re: self.re - other.re,
120 im: self.im - other.im,
121 }
122 }
123
124 #[inline]
125 pub fn scale(self, s: f32) -> Self {
126 Self {
127 re: self.re * s,
128 im: self.im * s,
129 }
130 }
131
132 #[inline]
134 pub fn exp_i(theta: f32) -> Self {
135 Self {
136 re: theta.cos(),
137 im: theta.sin(),
138 }
139 }
140
141 #[inline]
143 pub fn exp(self) -> Self {
144 let r = self.re.exp();
145 Self {
146 re: r * self.im.cos(),
147 im: r * self.im.sin(),
148 }
149 }
150}
151
152#[inline]
159pub fn distance_sq_3d(a: &[f32; 3], b: &[f32; 3]) -> f32 {
160 let dx = a[0] - b[0];
161 let dy = a[1] - b[1];
162 let dz = a[2] - b[2];
163 dx * dx + dy * dy + dz * dz
164}
165
166#[inline]
168pub fn distance_3d(a: &[f32; 3], b: &[f32; 3]) -> f32 {
169 distance_sq_3d(a, b).sqrt()
170}
171
172#[inline]
176pub fn gaussian_kernel(dist_sq: f32, sigma_sq: f32) -> f32 {
177 (-dist_sq / sigma_sq).exp()
178}
179
180#[inline]
182pub fn vec3_dot(a: [f32; 3], b: [f32; 3]) -> f32 {
183 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
184}
185
186#[inline]
188pub fn vec3_len(v: [f32; 3]) -> f32 {
189 vec3_dot(v, v).sqrt()
190}
191
192#[inline]
194pub fn vec3_scale(v: [f32; 3], s: f32) -> [f32; 3] {
195 [v[0] * s, v[1] * s, v[2] * s]
196}
197
198#[inline]
200pub fn vec3_cross(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
201 [
202 a[1] * b[2] - a[2] * b[1],
203 a[2] * b[0] - a[0] * b[2],
204 a[0] * b[1] - a[1] * b[0],
205 ]
206}
207
208#[inline]
210pub fn vec3_normalize(v: [f32; 3]) -> [f32; 3] {
211 let len = vec3_len(v);
212 if len < 1e-8 { [0.0; 3] } else { vec3_scale(v, 1.0 / len) }
213}
214
215#[inline]
225pub fn traveling_wave(x: f32, t: f32, k: f32, omega: f32, amplitude: f32) -> f32 {
226 amplitude * (k * x - omega * t).sin()
227}
228
229#[inline]
232pub fn standing_wave(x: f32, t: f32, k: f32, omega: f32, amplitude: f32) -> f32 {
233 amplitude * (k * x).sin() * (omega * t).cos()
234}
235
236#[inline]
241pub fn traveling_wave_2d(along: f32, perp: f32, t: f32, k: f32, omega: f32, amplitude: f32, taper: f32) -> f32 {
242 amplitude * (k * along - omega * t).sin() * (k * perp * taper).cos()
243}
244
245#[inline]
247pub fn wave_number(wavelength: f32) -> f32 {
248 std::f32::consts::TAU / wavelength
249}
250
251#[inline]
253pub fn angular_frequency(frequency: f32) -> f32 {
254 std::f32::consts::TAU * frequency
255}
256
257#[inline]
259pub fn dispersion_linear(k: f32, speed: f32) -> f32 {
260 speed * k
261}
262
263#[inline]
266pub fn wave_packet_gaussian(x: f32, t: f32, k0: f32, sigma: f32, group_velocity: f32) -> f32 {
267 let center = group_velocity * t;
268 let envelope = (-(x - center).powi(2) / (4.0 * sigma * sigma)).exp();
269 let carrier = (k0 * x - dispersion_linear(k0, group_velocity) * t).cos();
270 envelope * carrier
271}
272
273#[inline]
279pub fn golden_ratio() -> f32 {
280 (1.0 + 5.0f32.sqrt()) / 2.0
281}
282
283#[inline]
288pub fn golden_angle() -> f32 {
289 let phi = golden_ratio();
290 std::f32::consts::TAU / (phi * phi)
291}
292
293pub fn fibonacci_sphere(count: u32) -> Vec<[f32; 3]> {
299 let phi = golden_ratio();
300 let angle_inc = std::f32::consts::TAU * phi;
301 let mut points = Vec::with_capacity(count as usize);
302 for i in 0..count {
303 let t = (i as f32 + 0.5) / count as f32;
304 let polar = (1.0 - 2.0 * t).acos();
305 let azimuth = angle_inc * i as f32;
306 points.push([polar.sin() * azimuth.cos(), polar.cos(), polar.sin() * azimuth.sin()]);
307 }
308 points
309}
310
311pub fn phyllotaxis_disc(count: u32, radius: f32, rotation: f32) -> Vec<[f32; 3]> {
316 let ga = golden_angle();
317 let mut points = Vec::with_capacity(count as usize);
318 for i in 0..count {
319 let frac = (i as f32 + 0.5) / count as f32;
320 let r = frac.sqrt() * radius;
321 let theta = i as f32 * ga + rotation;
322 points.push([r * theta.cos(), 0.0, r * theta.sin()]);
323 }
324 points
325}
326
327#[inline]
333pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
334 a + (b - a) * t
335}
336
337#[inline]
339pub fn lerp3(a: [f32; 3], b: [f32; 3], t: f32) -> [f32; 3] {
340 [lerp(a[0], b[0], t), lerp(a[1], b[1], t), lerp(a[2], b[2], t)]
341}
342
343#[inline]
353pub fn exp_decay(current: f32, target: f32, rate: f32, dt: f32) -> f32 {
354 current + (target - current) * (1.0 - (-rate * dt).exp())
355}
356
357#[inline]
360pub fn smoothstep(edge0: f32, edge1: f32, x: f32) -> f32 {
361 let t = ((x - edge0) / (edge1 - edge0)).clamp(0.0, 1.0);
362 t * t * (3.0 - 2.0 * t)
363}
364
365#[inline]
377pub fn splitmix64(state: u64) -> (u64, u64) {
378 let s = state.wrapping_add(0x9E3779B97F4A7C15);
379 let mut z = s;
380 z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
381 z = (z ^ (z >> 27)).wrapping_mul(0x94D049BB133111EB);
382 z = z ^ (z >> 31);
383 (z, s)
384}
385
386#[inline]
388pub fn splitmix64_f32(state: u64) -> (f32, u64) {
389 let (val, next) = splitmix64(state);
390 ((val >> 40) as f32 / (1u64 << 24) as f32, next)
391}
392
393#[inline]
402pub fn atomic_f32_add(atom: &std::sync::atomic::AtomicU32, val: f32) {
403 use std::sync::atomic::Ordering;
404 loop {
405 let bits = atom.load(Ordering::Relaxed);
406 let current = f32::from_bits(bits);
407 let new = current + val;
408 match atom.compare_exchange_weak(bits, new.to_bits(), Ordering::Relaxed, Ordering::Relaxed) {
409 Ok(_) => break,
410 Err(_) => {} }
412 }
413}
414
415pub const HALF_NEIGHBORHOOD: [(i32, i32, i32); 14] = [
429 (0, 0, 0), (0, 0, 1),
431 (0, 1, -1),
432 (0, 1, 0),
433 (0, 1, 1),
434 (1, -1, -1),
435 (1, -1, 0),
436 (1, -1, 1),
437 (1, 0, -1),
438 (1, 0, 0),
439 (1, 0, 1),
440 (1, 1, -1),
441 (1, 1, 0),
442 (1, 1, 1),
443];
444
445#[inline]
448pub fn fnv1a_3d(ix: i32, iy: i32, iz: i32) -> u64 {
449 let mut h: u64 = 0xcbf29ce484222325;
450 for byte in ix
451 .to_le_bytes()
452 .iter()
453 .chain(iy.to_le_bytes().iter())
454 .chain(iz.to_le_bytes().iter())
455 {
456 h ^= *byte as u64;
457 h = h.wrapping_mul(0x100000001b3);
458 }
459 h
460}
461
462#[inline]
472pub fn adiabatic_fraction(step: u32, interval: u32, window: u32) -> f32 {
473 let phase = step % interval;
474 if step > 0 && phase < window {
475 1.0 / window as f32
476 } else {
477 0.0
478 }
479}
480
481#[inline]
490pub fn contact_impulse(v_along_normal: f32, restitution: f32, inv_mass_sum: f32) -> f32 {
491 if inv_mass_sum < 1e-8 {
492 return 0.0;
493 }
494 -(1.0 + restitution) * v_along_normal / inv_mass_sum
495}
496
497#[inline]
503pub fn baumgarte_correction(depth: f32, slop: f32, pct: f32, inv_mass_sum: f32) -> f32 {
504 if inv_mass_sum < 1e-8 {
505 return 0.0;
506 }
507 (depth - slop).max(0.0) * pct / inv_mass_sum
508}
509
510#[inline]
513pub fn coulomb_friction_clamp(jt: f32, j_normal_abs: f32, mu: f32) -> f32 {
514 jt.clamp(-j_normal_abs * mu, j_normal_abs * mu)
515}
516
517pub fn union_find_root(parent: &mut [usize], mut x: usize) -> usize {
520 while parent[x] != x {
521 parent[x] = parent[parent[x]]; x = parent[x];
523 }
524 x
525}
526
527pub fn union_find_merge(parent: &mut [usize], rank: &mut [u8], a: usize, b: usize) {
529 let ra = union_find_root(parent, a);
530 let rb = union_find_root(parent, b);
531 if ra == rb {
532 return;
533 }
534 if rank[ra] < rank[rb] {
535 parent[ra] = rb;
536 } else if rank[ra] > rank[rb] {
537 parent[rb] = ra;
538 } else {
539 parent[rb] = ra;
540 rank[ra] += 1;
541 }
542}
543
544pub fn slerp(a: [f32; 4], b: [f32; 4], t: f32) -> [f32; 4] {
551 let mut dot = a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
552 let mut b = b;
553 if dot < 0.0 {
554 b = [-b[0], -b[1], -b[2], -b[3]];
556 dot = -dot;
557 }
558 if dot > 0.9995 {
559 let mut r = [0.0f32; 4];
561 for i in 0..4 {
562 r[i] = a[i] + (b[i] - a[i]) * t;
563 }
564 let len = (r[0] * r[0] + r[1] * r[1] + r[2] * r[2] + r[3] * r[3]).sqrt();
565 if len > 1e-8 {
566 for i in 0..4 {
567 r[i] /= len;
568 }
569 }
570 return r;
571 }
572 let theta = dot.clamp(-1.0, 1.0).acos();
573 let sin_theta = theta.sin();
574 let wa = ((1.0 - t) * theta).sin() / sin_theta;
575 let wb = (t * theta).sin() / sin_theta;
576 [
577 a[0] * wa + b[0] * wb,
578 a[1] * wa + b[1] * wb,
579 a[2] * wa + b[2] * wb,
580 a[3] * wa + b[3] * wb,
581 ]
582}
583
584#[inline]
586pub fn remap(value: f32, in_min: f32, in_max: f32, out_min: f32, out_max: f32) -> f32 {
587 let t = (value - in_min) / (in_max - in_min);
588 out_min + (out_max - out_min) * t
589}
590
591#[inline]
594pub fn ease_in_out_cubic(t: f32) -> f32 {
595 if t < 0.5 {
596 4.0 * t * t * t
597 } else {
598 1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
599 }
600}
601
602#[inline]
604pub fn clamp01(v: f32) -> f32 {
605 v.clamp(0.0, 1.0)
606}
607
608#[cfg(test)]
613mod tests {
614 use super::*;
615
616 #[test]
617 fn complex_arithmetic() {
618 let a = Complex::new(1.0, 2.0);
619 let b = Complex::new(3.0, -1.0);
620 let c = a.mul(b); assert!((c.re - 5.0).abs() < 1e-6);
622 assert!((c.im - 5.0).abs() < 1e-6);
623 }
624
625 #[test]
626 fn complex_euler() {
627 let z = Complex::exp_i(std::f32::consts::PI); assert!((z.re - (-1.0)).abs() < 1e-5);
629 assert!(z.im.abs() < 1e-5);
630 }
631
632 #[test]
633 fn complex_conjugate() {
634 let z = Complex::new(3.0, 4.0);
635 let zc = z.conj();
636 assert_eq!(zc.re, 3.0);
637 assert_eq!(zc.im, -4.0);
638 assert!((z.mul(zc).im).abs() < 1e-6); }
640
641 #[test]
642 fn distance_sq() {
643 let a = [1.0, 2.0, 3.0];
644 let b = [4.0, 6.0, 3.0];
645 assert!((distance_sq_3d(&a, &b) - 25.0).abs() < 1e-6); }
647
648 #[test]
649 fn gaussian_at_center() {
650 assert!((gaussian_kernel(0.0, 1.0) - 1.0).abs() < 1e-6);
651 }
652
653 #[test]
654 fn gaussian_decays() {
655 let near = gaussian_kernel(1.0, 4.0);
656 let far = gaussian_kernel(9.0, 4.0);
657 assert!(near > far);
658 }
659
660 #[test]
661 fn traveling_wave_oscillates() {
662 let y0 = traveling_wave(0.0, 0.0, 1.0, 1.0, 1.0);
663 let y1 = traveling_wave(std::f32::consts::PI, 0.0, 1.0, 1.0, 1.0);
664 assert!(y0.abs() < 1e-6); assert!(y1.abs() < 1e-6); }
667
668 #[test]
669 fn wave_number_from_wavelength() {
670 let k = wave_number(1.0);
671 assert!((k - std::f32::consts::TAU).abs() < 1e-5);
672 }
673
674 #[test]
675 fn golden_ratio_value() {
676 assert!((golden_ratio() - 1.6180339).abs() < 1e-5);
677 }
678
679 #[test]
680 fn golden_angle_value() {
681 assert!((golden_angle() - 2.3999632).abs() < 1e-4);
682 }
683
684 #[test]
685 fn fibonacci_sphere_count() {
686 let pts = fibonacci_sphere(128);
687 assert_eq!(pts.len(), 128);
688 for p in &pts {
689 let len = vec3_len(*p);
690 assert!((len - 1.0).abs() < 0.01);
691 }
692 }
693
694 #[test]
695 fn phyllotaxis_disc_count() {
696 let pts = phyllotaxis_disc(64, 1.0, 0.0);
697 assert_eq!(pts.len(), 64);
698 }
699
700 #[test]
701 fn lerp_endpoints() {
702 assert_eq!(lerp(0.0, 10.0, 0.0), 0.0);
703 assert_eq!(lerp(0.0, 10.0, 1.0), 10.0);
704 assert!((lerp(0.0, 10.0, 0.5) - 5.0).abs() < 1e-6);
705 }
706
707 #[test]
708 fn exp_decay_converges() {
709 let mut val = 0.0f32;
710 for _ in 0..100 {
711 val = exp_decay(val, 1.0, 8.0, 1.0 / 60.0);
712 }
713 assert!((val - 1.0).abs() < 0.01);
714 }
715
716 #[test]
717 fn smoothstep_boundaries() {
718 assert!((smoothstep(0.0, 1.0, -0.5)).abs() < 1e-6);
719 assert!((smoothstep(0.0, 1.0, 1.5) - 1.0).abs() < 1e-6);
720 assert!((smoothstep(0.0, 1.0, 0.5) - 0.5).abs() < 0.1); }
722
723 #[test]
724 fn splitmix64_deterministic() {
725 let (a, _) = splitmix64(42);
726 let (b, _) = splitmix64(42);
727 assert_eq!(a, b);
728 }
729
730 #[test]
731 fn splitmix64_f32_range() {
732 let (val, _) = splitmix64_f32(42);
733 assert!(val >= 0.0 && val < 1.0);
734 }
735
736 #[test]
737 fn vec3_operations() {
738 let a = [1.0, 0.0, 0.0];
739 let b = [0.0, 1.0, 0.0];
740 let cross = vec3_cross(a, b);
741 assert!((cross[2] - 1.0).abs() < 1e-6); }
743
744 #[test]
745 fn half_neighborhood_14_cells() {
746 assert_eq!(HALF_NEIGHBORHOOD.len(), 14);
747 }
748
749 #[test]
750 fn fnv1a_deterministic() {
751 assert_eq!(fnv1a_3d(1, 2, 3), fnv1a_3d(1, 2, 3));
752 assert_ne!(fnv1a_3d(1, 2, 3), fnv1a_3d(3, 2, 1));
753 }
754
755 #[test]
756 fn adiabatic_in_window() {
757 assert!((adiabatic_fraction(1, 60, 10) - 0.1).abs() < 1e-6);
758 assert_eq!(adiabatic_fraction(15, 60, 10), 0.0); }
760
761 #[test]
762 fn contact_impulse_separating() {
763 let j = contact_impulse(1.0, 0.5, 2.0);
765 assert!(j < 0.0); }
767
768 #[test]
769 fn union_find_basic() {
770 let mut parent: Vec<usize> = (0..5).collect();
771 let mut rank = vec![0u8; 5];
772 union_find_merge(&mut parent, &mut rank, 0, 1);
773 union_find_merge(&mut parent, &mut rank, 2, 3);
774 union_find_merge(&mut parent, &mut rank, 1, 3);
775 assert_eq!(union_find_root(&mut parent, 0), union_find_root(&mut parent, 3));
776 }
777
778 #[test]
779 fn slerp_endpoints() {
780 let a = [0.0, 0.0, 0.0, 1.0]; let b = [0.0, 1.0, 0.0, 0.0]; let mid = slerp(a, b, 0.0);
783 assert!((mid[3] - 1.0).abs() < 0.01); }
785
786 #[test]
787 fn remap_basic() {
788 assert!((remap(5.0, 0.0, 10.0, 0.0, 100.0) - 50.0).abs() < 1e-6);
789 }
790
791 #[test]
792 fn ease_cubic_endpoints() {
793 assert!(ease_in_out_cubic(0.0).abs() < 1e-6);
794 assert!((ease_in_out_cubic(1.0) - 1.0).abs() < 1e-6);
795 }
796
797 #[test]
798 fn clamp01_bounds() {
799 assert_eq!(clamp01(-0.5), 0.0);
800 assert_eq!(clamp01(1.5), 1.0);
801 assert!((clamp01(0.5) - 0.5).abs() < 1e-6);
802 }
803}