Skip to main content

sklears_utils/
type_safety.rs

1//! Type safety utilities for compile-time validation and zero-cost abstractions
2//!
3//! This module provides phantom types, zero-cost wrappers, and compile-time
4//! validation utilities to ensure type safety in machine learning operations.
5
6use crate::{UtilsError, UtilsResult};
7use scirs2_core::ndarray::{Array1, Array2};
8use std::marker::PhantomData;
9
10// ===== PHANTOM TYPES FOR STATE VALIDATION =====
11
12/// Phantom type for untrained state
13pub struct Untrained;
14
15/// Phantom type for trained state
16pub struct Trained;
17
18/// Phantom type for validated data
19pub struct Validated;
20
21/// Phantom type for unvalidated data
22pub struct Unvalidated;
23
24/// State-based wrapper for ML models
25#[derive(Debug, Clone)]
26pub struct ModelState<T, State> {
27    pub inner: T,
28    _state: PhantomData<State>,
29}
30
31impl<T> ModelState<T, Untrained> {
32    /// Create a new untrained model
33    pub fn new(inner: T) -> Self {
34        Self {
35            inner,
36            _state: PhantomData,
37        }
38    }
39
40    /// Transition to trained state (only available for untrained models)
41    pub fn train(self) -> ModelState<T, Trained> {
42        ModelState {
43            inner: self.inner,
44            _state: PhantomData,
45        }
46    }
47}
48
49impl<T> ModelState<T, Trained> {
50    /// Predict (only available for trained models)
51    pub fn predict<F, Input, Output>(&self, predict_fn: F, input: Input) -> Output
52    where
53        F: Fn(&T, Input) -> Output,
54    {
55        predict_fn(&self.inner, input)
56    }
57
58    /// Reset to untrained state
59    pub fn reset(self) -> ModelState<T, Untrained> {
60        ModelState {
61            inner: self.inner,
62            _state: PhantomData,
63        }
64    }
65}
66
67// ===== VALIDATED DATA TYPES =====
68
69/// Data wrapper with validation state
70#[derive(Debug, Clone)]
71pub struct DataState<T, State> {
72    pub data: T,
73    _state: PhantomData<State>,
74}
75
76impl<T> DataState<T, Unvalidated> {
77    /// Create new unvalidated data
78    pub fn new(data: T) -> Self {
79        Self {
80            data,
81            _state: PhantomData,
82        }
83    }
84
85    /// Validate data and transition to validated state
86    pub fn validate<F>(self, validator: F) -> UtilsResult<DataState<T, Validated>>
87    where
88        F: FnOnce(&T) -> UtilsResult<()>,
89    {
90        validator(&self.data)?;
91        Ok(DataState {
92            data: self.data,
93            _state: PhantomData,
94        })
95    }
96}
97
98impl<T> DataState<T, Validated> {
99    /// Access validated data (only available after validation)
100    pub fn as_validated(&self) -> &T {
101        &self.data
102    }
103
104    /// Transform validated data while preserving validation state
105    pub fn map<U, F>(self, transform: F) -> DataState<U, Validated>
106    where
107        F: FnOnce(T) -> U,
108    {
109        DataState {
110            data: transform(self.data),
111            _state: PhantomData,
112        }
113    }
114}
115
116// ===== DIMENSIONAL TYPE SAFETY =====
117
118/// Phantom types for dimensions
119pub struct D1;
120pub struct D2;
121pub struct D3;
122
123/// Dimensionally-typed array wrapper
124#[derive(Debug, Clone)]
125pub struct TypedArray<T, D> {
126    data: T,
127    _dimension: PhantomData<D>,
128}
129
130impl<T> TypedArray<Array1<T>, D1> {
131    /// Create a 1D typed array
132    pub fn new_1d(array: Array1<T>) -> Self {
133        Self {
134            data: array,
135            _dimension: PhantomData,
136        }
137    }
138
139    /// Get the underlying 1D array
140    pub fn as_array1(&self) -> &Array1<T> {
141        &self.data
142    }
143
144    /// Convert to 2D array (single row)
145    pub fn to_2d(self) -> TypedArray<Array2<T>, D2>
146    where
147        T: Clone,
148    {
149        let shape = (1, self.data.len());
150        let data =
151            Array2::from_shape_vec(shape, self.data.to_vec()).expect("operation should succeed");
152        TypedArray {
153            data,
154            _dimension: PhantomData,
155        }
156    }
157}
158
159impl<T> TypedArray<Array2<T>, D2> {
160    /// Create a 2D typed array
161    pub fn new_2d(array: Array2<T>) -> Self {
162        Self {
163            data: array,
164            _dimension: PhantomData,
165        }
166    }
167
168    /// Get the underlying 2D array
169    pub fn as_array2(&self) -> &Array2<T> {
170        &self.data
171    }
172
173    /// Get shape information
174    pub fn shape(&self) -> (usize, usize) {
175        let shape = self.data.shape();
176        (shape[0], shape[1])
177    }
178
179    /// Flatten to 1D array
180    pub fn flatten(self) -> TypedArray<Array1<T>, D1>
181    where
182        T: Clone,
183    {
184        let (vec, offset) = self.data.into_raw_vec_and_offset();
185        assert_eq!(offset, Some(0), "Array offset must be zero for conversion");
186        let data = Array1::from_vec(vec);
187        TypedArray {
188            data,
189            _dimension: PhantomData,
190        }
191    }
192}
193
194// ===== UNITS AND MEASUREMENTS =====
195
196/// Unit types for type-safe measurements
197pub trait Unit: 'static {
198    const NAME: &'static str;
199}
200
201pub struct Meters;
202pub struct Seconds;
203pub struct Kilograms;
204pub struct Pixels;
205
206impl Unit for Meters {
207    const NAME: &'static str = "meters";
208}
209
210impl Unit for Seconds {
211    const NAME: &'static str = "seconds";
212}
213
214impl Unit for Kilograms {
215    const NAME: &'static str = "kilograms";
216}
217
218impl Unit for Pixels {
219    const NAME: &'static str = "pixels";
220}
221
222/// Type-safe measurement with units
223#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
224pub struct Measurement<T, U: Unit> {
225    value: T,
226    _unit: PhantomData<U>,
227}
228
229impl<T, U: Unit> Measurement<T, U> {
230    /// Create a new measurement
231    pub fn new(value: T) -> Self {
232        Self {
233            value,
234            _unit: PhantomData,
235        }
236    }
237
238    /// Get the value
239    pub fn value(&self) -> &T {
240        &self.value
241    }
242
243    /// Convert to different unit (unsafe, requires manual verification)
244    ///
245    /// # Safety
246    ///
247    /// The caller must ensure that the conversion between units is mathematically valid
248    /// and that the value makes sense in the target unit system.
249    pub unsafe fn convert_unit<V: Unit>(self) -> Measurement<T, V> {
250        Measurement {
251            value: self.value,
252            _unit: PhantomData,
253        }
254    }
255}
256
257impl<T, U: Unit> std::ops::Add for Measurement<T, U>
258where
259    T: std::ops::Add<Output = T>,
260{
261    type Output = Self;
262
263    fn add(self, other: Self) -> Self::Output {
264        Self {
265            value: self.value + other.value,
266            _unit: PhantomData,
267        }
268    }
269}
270
271impl<T, U: Unit> std::ops::Sub for Measurement<T, U>
272where
273    T: std::ops::Sub<Output = T>,
274{
275    type Output = Self;
276
277    fn sub(self, other: Self) -> Self::Output {
278        Self {
279            value: self.value - other.value,
280            _unit: PhantomData,
281        }
282    }
283}
284
285// ===== COMPILE-TIME VALIDATION =====
286
287/// Trait for compile-time shape validation
288pub trait ShapeValidation {
289    type Shape;
290    fn validate_shape(shape: Self::Shape) -> bool;
291}
292
293/// Shape constraint: exactly N elements
294pub struct ExactSize<const N: usize>;
295
296impl<const N: usize> ShapeValidation for ExactSize<N> {
297    type Shape = usize;
298
299    fn validate_shape(shape: Self::Shape) -> bool {
300        shape == N
301    }
302}
303
304/// Shape constraint: minimum N elements
305pub struct MinSize<const N: usize>;
306
307impl<const N: usize> ShapeValidation for MinSize<N> {
308    type Shape = usize;
309
310    fn validate_shape(shape: Self::Shape) -> bool {
311        shape >= N
312    }
313}
314
315/// Shape-validated array
316pub struct ValidatedArray<T, V: ShapeValidation> {
317    data: Array1<T>,
318    _validator: PhantomData<V>,
319}
320
321impl<T, V: ShapeValidation<Shape = usize>> ValidatedArray<T, V> {
322    /// Create a validated array (compile-time check)
323    pub fn new(data: Array1<T>) -> Option<Self> {
324        if V::validate_shape(data.len()) {
325            Some(Self {
326                data,
327                _validator: PhantomData,
328            })
329        } else {
330            None
331        }
332    }
333
334    /// Access the validated data
335    pub fn data(&self) -> &Array1<T> {
336        &self.data
337    }
338}
339
340// ===== ZERO-COST ABSTRACTIONS =====
341
342/// Zero-cost wrapper for normalized values [0, 1]
343#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
344pub struct Normalized<T>(T);
345
346impl<T> Normalized<T> {
347    /// Create a normalized value (unsafe - assumes value is in [0, 1])
348    ///
349    /// # Safety
350    ///
351    /// The caller must ensure that the value is within the range [0, 1].
352    /// Using values outside this range will result in undefined behavior.
353    pub unsafe fn new_unchecked(value: T) -> Self {
354        Self(value)
355    }
356
357    /// Get the inner value
358    pub fn get(self) -> T {
359        self.0
360    }
361}
362
363impl Normalized<f64> {
364    /// Create a normalized value with validation
365    pub fn new(value: f64) -> UtilsResult<Self> {
366        if (0.0..=1.0).contains(&value) {
367            Ok(Self(value))
368        } else {
369            Err(UtilsError::InvalidParameter(format!(
370                "Value {value} is not in range [0, 1]"
371            )))
372        }
373    }
374
375    /// Clamp value to [0, 1] range
376    pub fn clamp(value: f64) -> Self {
377        Self(value.clamp(0.0, 1.0))
378    }
379}
380
381/// Zero-cost wrapper for positive values
382#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
383pub struct Positive<T>(T);
384
385impl<T> Positive<T> {
386    /// Get the inner value
387    pub fn get(self) -> T {
388        self.0
389    }
390}
391
392impl Positive<f64> {
393    /// Create a positive value with validation
394    pub fn new(value: f64) -> UtilsResult<Self> {
395        if value > 0.0 {
396            Ok(Self(value))
397        } else {
398            Err(UtilsError::InvalidParameter(format!(
399                "Value {value} is not positive"
400            )))
401        }
402    }
403}
404
405/// Zero-cost wrapper for non-negative values
406#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
407pub struct NonNegative<T>(T);
408
409impl<T> NonNegative<T> {
410    /// Get the inner value
411    pub fn get(self) -> T {
412        self.0
413    }
414}
415
416impl NonNegative<f64> {
417    /// Create a non-negative value with validation
418    pub fn new(value: f64) -> UtilsResult<Self> {
419        if value >= 0.0 {
420            Ok(Self(value))
421        } else {
422            Err(UtilsError::InvalidParameter(format!(
423                "Value {value} is negative"
424            )))
425        }
426    }
427}
428
429// ===== COMPILE-TIME ASSERTIONS =====
430
431/// Compile-time assertion macro
432#[macro_export]
433macro_rules! const_assert {
434    ($condition:expr) => {
435        const _: () = if !$condition {
436            panic!("Compile-time assertion failed");
437        } else {
438            ()
439        };
440    };
441}
442
443/// Compile-time shape assertion
444#[macro_export]
445macro_rules! assert_shape {
446    ($array:expr, $expected:expr) => {
447        if $array.shape() != $expected {
448            return Err(UtilsError::ShapeMismatch {
449                expected: $expected.to_vec(),
450                actual: $array.shape().to_vec(),
451            });
452        }
453    };
454}
455
456// ===== TYPE-LEVEL COMPUTATION =====
457
458/// Type-level arithmetic for compile-time computation
459pub trait TypeNum {
460    const VALUE: usize;
461}
462
463pub struct Zero;
464pub struct One;
465pub struct Two;
466pub struct Three;
467
468impl TypeNum for Zero {
469    const VALUE: usize = 0;
470}
471impl TypeNum for One {
472    const VALUE: usize = 1;
473}
474impl TypeNum for Two {
475    const VALUE: usize = 2;
476}
477impl TypeNum for Three {
478    const VALUE: usize = 3;
479}
480
481/// Add two type-level numbers
482pub trait Add<Rhs> {
483    type Output: TypeNum;
484}
485
486impl Add<Zero> for Zero {
487    type Output = Zero;
488}
489impl Add<One> for Zero {
490    type Output = One;
491}
492impl Add<Two> for Zero {
493    type Output = Two;
494}
495impl Add<Zero> for One {
496    type Output = One;
497}
498impl Add<One> for One {
499    type Output = Two;
500}
501impl Add<Two> for One {
502    type Output = Three;
503}
504
505/// Compile-time validated matrix multiplication
506pub struct MatrixMul<L: TypeNum, M: TypeNum, N: TypeNum> {
507    _phantom: PhantomData<(L, M, N)>,
508}
509
510impl<L: TypeNum, M: TypeNum, N: TypeNum> MatrixMul<L, M, N> {
511    /// Validate matrix multiplication at compile time
512    pub fn multiply(left: &Array2<f64>, right: &Array2<f64>) -> UtilsResult<Array2<f64>> {
513        // Runtime validation (would be compile-time in a full implementation)
514        let left_shape = left.shape();
515        let right_shape = right.shape();
516
517        if left_shape[1] != right_shape[0] {
518            return Err(UtilsError::ShapeMismatch {
519                expected: vec![left_shape[0], right_shape[1]],
520                actual: vec![left_shape[0], left_shape[1], right_shape[0], right_shape[1]],
521            });
522        }
523
524        Ok(left.dot(right))
525    }
526}
527
528#[allow(non_snake_case)]
529#[cfg(test)]
530mod tests {
531    use super::*;
532
533    #[test]
534    fn test_model_state_transitions() {
535        #[derive(Debug, Clone)]
536        struct MockModel {
537            value: i32,
538        }
539
540        let model = MockModel { value: 42 };
541        let untrained = ModelState::new(model);
542
543        // Can only train untrained models
544        let trained = untrained.train();
545
546        // Can only predict with trained models
547        let result = trained.predict(|model, input: i32| model.value + input, 10);
548        assert_eq!(result, 52);
549
550        // Can reset trained model to untrained
551        let _reset = trained.reset();
552    }
553
554    #[test]
555    fn test_data_validation() {
556        let data = vec![1, 2, 3, 4, 5];
557        let unvalidated = DataState::new(data);
558
559        // Validate that all elements are positive
560        let validated = unvalidated
561            .validate(|data| {
562                if data.iter().all(|&x| x > 0) {
563                    Ok(())
564                } else {
565                    Err(UtilsError::InvalidParameter(
566                        "Negative values found".to_string(),
567                    ))
568                }
569            })
570            .expect("operation should succeed");
571
572        // Can access validated data
573        let validated_data = validated.as_validated();
574        assert_eq!(validated_data.len(), 5);
575
576        // Transform while preserving validation
577        let transformed = validated.map(|data| data.len());
578        assert_eq!(*transformed.as_validated(), 5);
579    }
580
581    #[test]
582    fn test_typed_arrays() {
583        let array1d = Array1::from_vec(vec![1.0, 2.0, 3.0]);
584        let typed1d = TypedArray::new_1d(array1d);
585
586        // Convert to 2D
587        let typed2d = typed1d.to_2d();
588        assert_eq!(typed2d.shape(), (1, 3));
589
590        // Flatten back to 1D
591        let flattened = typed2d.flatten();
592        assert_eq!(flattened.as_array1().len(), 3);
593    }
594
595    #[test]
596    fn test_measurements() {
597        let distance1 = Measurement::<f64, Meters>::new(10.0);
598        let distance2 = Measurement::<f64, Meters>::new(5.0);
599
600        let total_distance = distance1 + distance2;
601        assert_eq!(*total_distance.value(), 15.0);
602
603        let _time = Measurement::<f64, Seconds>::new(2.0);
604        // This would not compile: distance1 + time (different units)
605    }
606
607    #[test]
608    fn test_normalized_values() {
609        // Valid normalized value
610        let norm1 = Normalized::new(0.5).expect("operation should succeed");
611        assert_eq!(norm1.get(), 0.5);
612
613        // Invalid normalized value
614        assert!(Normalized::new(1.5).is_err());
615
616        // Clamped value
617        let norm2 = Normalized::clamp(1.5);
618        assert_eq!(norm2.get(), 1.0);
619    }
620
621    #[test]
622    fn test_positive_values() {
623        let pos = Positive::new(5.0).expect("operation should succeed");
624        assert_eq!(pos.get(), 5.0);
625
626        assert!(Positive::new(-1.0).is_err());
627        assert!(Positive::new(0.0).is_err());
628    }
629
630    #[test]
631    fn test_validated_arrays() {
632        let data = Array1::from_vec(vec![1, 2, 3]);
633
634        // Should succeed for ExactSize<3>
635        let validated = ValidatedArray::<i32, ExactSize<3>>::new(data.clone());
636        assert!(validated.is_some());
637
638        // Should fail for ExactSize<5>
639        let validated = ValidatedArray::<i32, ExactSize<5>>::new(data.clone());
640        assert!(validated.is_none());
641
642        // Should succeed for MinSize<2>
643        let validated = ValidatedArray::<i32, MinSize<2>>::new(data);
644        assert!(validated.is_some());
645    }
646
647    #[test]
648    fn test_matrix_multiplication_validation() {
649        let left = Array2::from_shape_vec((2, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0])
650            .expect("operation should succeed");
651        let right = Array2::from_shape_vec((3, 2), vec![7.0, 8.0, 9.0, 10.0, 11.0, 12.0])
652            .expect("operation should succeed");
653
654        let result = MatrixMul::<Two, Three, Two>::multiply(&left, &right)
655            .expect("operation should succeed");
656        assert_eq!(result.shape(), &[2, 2]);
657
658        // Should fail with incompatible shapes
659        let wrong_right = Array2::from_shape_vec((2, 2), vec![1.0, 2.0, 3.0, 4.0])
660            .expect("operation should succeed");
661        assert!(MatrixMul::<Two, Three, Two>::multiply(&left, &wrong_right).is_err());
662    }
663}