Skip to main content

oxicuda_quantum/
handle.rs

1//! Session handle for `oxicuda-quantum`.
2
3// ─── SmVersion ───────────────────────────────────────────────────────────────
4
5/// GPU SM version as a raw `u32` (major*10 + minor).
6#[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// ─── LcgRng ──────────────────────────────────────────────────────────────────
39
40/// Knuth MMIX 64-bit LCG with Box-Muller normal sampling.
41#[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// ─── QuantumHandle ───────────────────────────────────────────────────────────
91
92/// Lightweight session descriptor for quantum operations.
93#[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}