oxicuda_quantum/
handle.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct SmVersion(pub u32);
8
9impl SmVersion {
10 #[must_use]
11 #[inline]
12 pub fn as_u32(self) -> u32 {
13 self.0
14 }
15
16 #[must_use]
17 pub fn ptx_version_str(self) -> &'static str {
18 match self.0 {
19 v if v >= 100 => "8.7",
20 v if v >= 90 => "8.4",
21 v if v >= 80 => "8.0",
22 _ => "7.5",
23 }
24 }
25
26 #[must_use]
27 pub fn target_str(self) -> String {
28 format!("sm_{}", self.0)
29 }
30}
31
32impl std::fmt::Display for SmVersion {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 write!(f, "SM {}.{}", self.0 / 10, self.0 % 10)
35 }
36}
37
38#[derive(Debug, Clone)]
42pub struct LcgRng {
43 state: u64,
44}
45
46impl LcgRng {
47 #[must_use]
48 pub fn new(seed: u64) -> Self {
49 Self {
50 state: seed.wrapping_add(1_442_695_040_888_963_407),
51 }
52 }
53
54 #[inline]
55 pub fn next_u32(&mut self) -> u32 {
56 self.state = self
57 .state
58 .wrapping_mul(6_364_136_223_846_793_005)
59 .wrapping_add(1_442_695_040_888_963_407);
60 ((self.state >> 33) ^ self.state) as u32
61 }
62
63 #[inline]
64 pub fn next_u64(&mut self) -> u64 {
65 let lo = self.next_u32() as u64;
66 let hi = self.next_u32() as u64;
67 (hi << 32) | lo
68 }
69
70 #[inline]
71 pub fn next_f32(&mut self) -> f32 {
72 (self.next_u32() >> 8) as f32 / 16_777_216.0
73 }
74
75 pub fn next_normal(&mut self) -> f32 {
76 let u1 = self.next_f32().max(1e-12);
77 let u2 = self.next_f32();
78 (-2.0 * u1.ln()).sqrt() * (2.0 * std::f32::consts::PI * u2).cos()
79 }
80
81 pub fn next_normal_pair(&mut self) -> (f32, f32) {
82 let u1 = self.next_f32().max(1e-12);
83 let u2 = self.next_f32();
84 let r = (-2.0 * u1.ln()).sqrt();
85 let theta = 2.0 * std::f32::consts::PI * u2;
86 (r * theta.cos(), r * theta.sin())
87 }
88}
89
90#[derive(Debug, Clone)]
94pub struct QuantumHandle {
95 pub sm: SmVersion,
96 pub rng: LcgRng,
97}
98
99impl QuantumHandle {
100 #[must_use]
101 pub fn new(sm_version: u32, seed: u64) -> Self {
102 Self {
103 sm: SmVersion(sm_version),
104 rng: LcgRng::new(seed),
105 }
106 }
107
108 #[must_use]
109 pub fn sm(&self) -> SmVersion {
110 self.sm
111 }
112
113 #[must_use]
114 pub fn rng_mut(&mut self) -> &mut LcgRng {
115 &mut self.rng
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn sm_version_ptx_strings() {
125 assert_eq!(SmVersion(75).ptx_version_str(), "7.5");
126 assert_eq!(SmVersion(80).ptx_version_str(), "8.0");
127 assert_eq!(SmVersion(90).ptx_version_str(), "8.4");
128 assert_eq!(SmVersion(100).ptx_version_str(), "8.7");
129 assert_eq!(SmVersion(120).ptx_version_str(), "8.7");
130 }
131
132 #[test]
133 fn lcg_determinism() {
134 let mut a = LcgRng::new(7);
135 let mut b = LcgRng::new(7);
136 for _ in 0..100 {
137 assert_eq!(a.next_u32(), b.next_u32());
138 }
139 }
140
141 #[test]
142 fn lcg_f32_in_range() {
143 let mut rng = LcgRng::new(11);
144 for _ in 0..1000 {
145 let v = rng.next_f32();
146 assert!((0.0..1.0).contains(&v), "f32={v}");
147 }
148 }
149
150 #[test]
151 fn handle_new() {
152 let h = QuantumHandle::new(80, 42);
153 assert_eq!(h.sm(), SmVersion(80));
154 }
155}