Skip to main content

mrc/engine/
convert.rs

1//! Type Conversion Layer (Layer 4 of the pipeline)
2//!
3//! This module implements voxel type conversions:
4//! ```text
5//! Typed voxel values → Converted voxel values
6//! ```
7//!
8//! Conversions include:
9//! - Int8 → Float32
10//! - Int16 → Float32
11//! - Uint16 → Float32
12//! - Float16 → Float32
13//! - Float32 → Int16
14//! - Int16Complex ↔ Float32Complex
15//! - Packed4Bit → all integer types
16//! - u8 → all types
17
18use crate::mode::{Float32Complex, Int16Complex, Packed4Bit};
19use alloc::vec::Vec;
20
21#[cfg(feature = "simd")]
22use super::simd;
23
24/// Trait for converting between voxel types.
25///
26/// This enables the type-level conversion graph described in engine.md.
27pub trait Convert<S>: Sized {
28    /// Convert a source value to the destination type
29    fn convert(src: S) -> Self;
30}
31
32/// Attempt SIMD-accelerated conversion from source slice to destination type.
33/// 
34/// Returns Some(Vec<D>) if SIMD conversion is available, None otherwise.
35/// Currently supports: i8/i16/u16/u8 → f32
36#[cfg(feature = "simd")]
37pub fn try_simd_convert<S, D>(src: &[S]) -> Option<Vec<D>>
38where
39    D: Convert<S> + 'static,
40    S: 'static,
41{
42    use core::any::TypeId;
43    
44    // Check if destination is f32
45    if TypeId::of::<D>() != TypeId::of::<f32>() {
46        return None;
47    }
48    
49    // Try each source type
50    let result_f32: Vec<f32> = if TypeId::of::<S>() == TypeId::of::<i8>() {
51        simd::convert_i8_to_f32_simd(unsafe { core::slice::from_raw_parts(src.as_ptr() as *const i8, src.len()) })
52    } else if TypeId::of::<S>() == TypeId::of::<i16>() {
53        simd::convert_i16_to_f32_simd(unsafe { core::slice::from_raw_parts(src.as_ptr() as *const i16, src.len()) })
54    } else if TypeId::of::<S>() == TypeId::of::<u16>() {
55        simd::convert_u16_to_f32_simd(unsafe { core::slice::from_raw_parts(src.as_ptr() as *const u16, src.len()) })
56    } else if TypeId::of::<S>() == TypeId::of::<u8>() {
57        simd::convert_u8_to_f32_simd(unsafe { core::slice::from_raw_parts(src.as_ptr() as *const u8, src.len()) })
58    } else {
59        return None;
60    };
61    
62    // Safety: we verified D is f32, and result_f32 is Vec<f32>
63    // Vec<f32> and Vec<D> have same layout when D is f32
64    Some(unsafe { core::mem::transmute::<Vec<f32>, Vec<D>>(result_f32) })
65}
66
67/// Fallback when SIMD is disabled
68#[cfg(not(feature = "simd"))]
69pub fn try_simd_convert<S, D: Convert<S>>(_src: &[S]) -> Option<Vec<D>> {
70    None
71}
72
73/// Attempt SIMD-accelerated conversion from f32 to integer types (write path).
74///
75/// Returns Some(Vec<D>) if SIMD conversion is available, None otherwise.
76/// Currently supports: f32 → i8/i16/u16/u8
77#[cfg(feature = "simd")]
78pub fn try_simd_convert_reverse<S, D>(src: &[S]) -> Option<Vec<D>>
79where
80    S: 'static,
81    D: Convert<S> + 'static,
82{
83    use core::any::TypeId;
84
85    // Check if source is f32
86    if TypeId::of::<S>() != TypeId::of::<f32>() {
87        return None;
88    }
89
90    let _src_f32: &[f32] = unsafe { core::slice::from_raw_parts(src.as_ptr() as *const f32, src.len()) };
91
92    // For write path, we use scalar conversion since SIMD conversion with clamping
93    // is complex. The main benefit is on the read path (i16/u16 → f32).
94    // Future: Add AVX-512 or NEON saturating conversion instructions.
95    None
96}
97
98/// Fallback when SIMD is disabled
99#[cfg(not(feature = "simd"))]
100pub fn try_simd_convert_reverse<S, D: Convert<S>>(_src: &[S]) -> Option<Vec<D>> {
101    None
102}
103
104// === Conversions TO Float32 ===
105
106impl Convert<i8> for f32 {
107    #[inline]
108    fn convert(src: i8) -> Self {
109        src as f32
110    }
111}
112
113/// Batch conversion from i8 to f32 using SIMD when available.
114#[cfg(feature = "simd")]
115pub fn convert_i8_slice_to_f32(src: &[i8]) -> Vec<f32> {
116    simd::convert_i8_to_f32_simd(src)
117}
118
119/// Batch conversion from i8 to f32 (scalar fallback).
120#[cfg(not(feature = "simd"))]
121pub fn convert_i8_slice_to_f32(src: &[i8]) -> Vec<f32> {
122    src.iter().map(|&x| x as f32).collect()
123}
124
125/// Batch conversion from i16 to f32 using SIMD when available.
126#[cfg(feature = "simd")]
127pub fn convert_i16_slice_to_f32(src: &[i16]) -> Vec<f32> {
128    simd::convert_i16_to_f32_simd(src)
129}
130
131/// Batch conversion from i16 to f32 (scalar fallback).
132#[cfg(not(feature = "simd"))]
133pub fn convert_i16_slice_to_f32(src: &[i16]) -> Vec<f32> {
134    src.iter().map(|&x| x as f32).collect()
135}
136
137/// Batch conversion from u16 to f32 using SIMD when available.
138#[cfg(feature = "simd")]
139pub fn convert_u16_slice_to_f32(src: &[u16]) -> Vec<f32> {
140    simd::convert_u16_to_f32_simd(src)
141}
142
143/// Batch conversion from u16 to f32 (scalar fallback).
144#[cfg(not(feature = "simd"))]
145pub fn convert_u16_slice_to_f32(src: &[u16]) -> Vec<f32> {
146    src.iter().map(|&x| x as f32).collect()
147}
148
149/// Batch conversion from u8 to f32 using SIMD when available.
150#[cfg(feature = "simd")]
151pub fn convert_u8_slice_to_f32(src: &[u8]) -> Vec<f32> {
152    simd::convert_u8_to_f32_simd(src)
153}
154
155/// Batch conversion from u8 to f32 (scalar fallback).
156#[cfg(not(feature = "simd"))]
157pub fn convert_u8_slice_to_f32(src: &[u8]) -> Vec<f32> {
158    src.iter().map(|&x| x as f32).collect()
159}
160
161/// Batch conversion from f16 to f32.
162#[cfg(feature = "f16")]
163pub fn convert_f16_slice_to_f32(src: &[f16]) -> Vec<f32> {
164    // Note: AVX-512 FP16 would provide hardware acceleration but requires
165    // very recent CPUs. For now, use scalar conversion which is fast enough
166    // for typical cryo-EM intermediate files.
167    src.iter().map(|&x| x as f32).collect()
168}
169
170/// Batch conversion from f32 to f16.
171#[cfg(feature = "f16")]
172pub fn convert_f32_slice_to_f16(src: &[f32]) -> Vec<f16> {
173    src.iter().map(|&x| x as f16).collect()
174}
175
176impl Convert<u8> for f32 {
177    #[inline]
178    fn convert(src: u8) -> Self {
179        src as f32
180    }
181}
182
183impl Convert<i16> for f32 {
184    #[inline]
185    fn convert(src: i16) -> Self {
186        src as f32
187    }
188}
189
190impl Convert<u16> for f32 {
191    #[inline]
192    fn convert(src: u16) -> Self {
193        src as f32
194    }
195}
196
197#[cfg(feature = "f16")]
198impl Convert<f16> for f32 {
199    #[inline]
200    fn convert(src: f16) -> Self {
201        src as f32
202    }
203}
204
205impl Convert<f32> for f32 {
206    #[inline]
207    fn convert(src: f32) -> Self {
208        src
209    }
210}
211
212// === Conversions FROM Float32 ===
213
214impl Convert<f32> for i8 {
215    #[inline]
216    fn convert(src: f32) -> Self {
217        src.clamp(i8::MIN as f32, i8::MAX as f32) as i8
218    }
219}
220
221impl Convert<f32> for u8 {
222    #[inline]
223    fn convert(src: f32) -> Self {
224        src.clamp(u8::MIN as f32, u8::MAX as f32) as u8
225    }
226}
227
228impl Convert<f32> for i16 {
229    #[inline]
230    fn convert(src: f32) -> Self {
231        src.clamp(i16::MIN as f32, i16::MAX as f32) as i16
232    }
233}
234
235impl Convert<f32> for u16 {
236    #[inline]
237    fn convert(src: f32) -> Self {
238        src.clamp(u16::MIN as f32, u16::MAX as f32) as u16
239    }
240}
241
242#[cfg(feature = "f16")]
243impl Convert<f32> for f16 {
244    #[inline]
245    fn convert(src: f32) -> Self {
246        src as f16
247    }
248}
249
250// === Identity conversions ===
251
252impl Convert<i8> for i8 {
253    #[inline]
254    fn convert(src: i8) -> Self {
255        src
256    }
257}
258
259impl Convert<i16> for i16 {
260    #[inline]
261    fn convert(src: i16) -> Self {
262        src
263    }
264}
265
266impl Convert<u16> for u16 {
267    #[inline]
268    fn convert(src: u16) -> Self {
269        src
270    }
271}
272
273// === Complex type conversions ===
274
275impl Convert<Int16Complex> for Float32Complex {
276    #[inline]
277    fn convert(src: Int16Complex) -> Self {
278        Self {
279            real: src.real as f32,
280            imag: src.imag as f32,
281        }
282    }
283}
284
285impl Convert<Float32Complex> for Int16Complex {
286    #[inline]
287    fn convert(src: Float32Complex) -> Self {
288        Self {
289            real: src.real.clamp(i16::MIN as f32, i16::MAX as f32) as i16,
290            imag: src.imag.clamp(i16::MIN as f32, i16::MAX as f32) as i16,
291        }
292    }
293}
294
295impl Convert<Int16Complex> for Int16Complex {
296    #[inline]
297    fn convert(src: Int16Complex) -> Self {
298        src
299    }
300}
301
302impl Convert<Float32Complex> for Float32Complex {
303    #[inline]
304    fn convert(src: Float32Complex) -> Self {
305        src
306    }
307}
308
309// === Integer-to-Integer Conversions ===
310
311impl Convert<i8> for i16 {
312    #[inline]
313    fn convert(src: i8) -> Self {
314        src as i16
315    }
316}
317
318impl Convert<i16> for i8 {
319    #[inline]
320    fn convert(src: i16) -> Self {
321        src.clamp(i8::MIN as i16, i8::MAX as i16) as i8
322    }
323}
324
325impl Convert<u8> for i16 {
326    #[inline]
327    fn convert(src: u8) -> Self {
328        src as i16
329    }
330}
331
332impl Convert<i16> for u8 {
333    #[inline]
334    fn convert(src: i16) -> Self {
335        src.clamp(u8::MIN as i16, u8::MAX as i16) as u8
336    }
337}
338
339impl Convert<u8> for u16 {
340    #[inline]
341    fn convert(src: u8) -> Self {
342        src as u16
343    }
344}
345
346impl Convert<u16> for u8 {
347    #[inline]
348    fn convert(src: u16) -> Self {
349        src.clamp(u8::MIN as u16, u8::MAX as u16) as u8
350    }
351}
352
353impl Convert<i8> for u16 {
354    #[inline]
355    fn convert(src: i8) -> Self {
356        src.max(0) as u16
357    }
358}
359
360impl Convert<u16> for i8 {
361    #[inline]
362    fn convert(src: u16) -> Self {
363        (src as i16).clamp(i8::MIN as i16, i8::MAX as i16) as i8
364    }
365}
366
367impl Convert<i16> for u16 {
368    #[inline]
369    fn convert(src: i16) -> Self {
370        src.max(0) as u16
371    }
372}
373
374impl Convert<u16> for i16 {
375    #[inline]
376    fn convert(src: u16) -> Self {
377        src.min(i16::MAX as u16) as i16
378    }
379}
380
381// === Packed4Bit Conversions ===
382
383impl Convert<Packed4Bit> for u8 {
384    #[inline]
385    fn convert(src: Packed4Bit) -> Self {
386        src.first()
387    }
388}
389
390impl Convert<Packed4Bit> for i8 {
391    #[inline]
392    fn convert(src: Packed4Bit) -> Self {
393        // Treat as signed: 0-7 positive, 8-15 negative (two's complement)
394        let val = src.first();
395        if val & 0x08 != 0 {
396            (val | 0xF0) as i8 // Sign extend
397        } else {
398            val as i8
399        }
400    }
401}
402
403impl Convert<Packed4Bit> for f32 {
404    #[inline]
405    fn convert(src: Packed4Bit) -> Self {
406        // Treat as unsigned 4-bit value
407        src.first() as f32
408    }
409}
410
411// === Identity Conversions (completing the matrix) ===
412
413impl Convert<u8> for u8 {
414    #[inline]
415    fn convert(src: u8) -> Self {
416        src
417    }
418}