1use std::mem::{align_of, size_of};
20
21use crate::format::ModelType;
22
23#[derive(Debug, Clone)]
52pub struct TruenoNativeModel {
53 pub model_type: ModelType,
55
56 pub n_params: u32,
58
59 pub n_features: u32,
61
62 pub n_outputs: u32,
64
65 pub params: Option<AlignedVec<f32>>,
67
68 pub bias: Option<AlignedVec<f32>>,
70
71 pub extra: Option<ModelExtra>,
73}
74
75impl TruenoNativeModel {
76 #[must_use]
78 pub const fn new(
79 model_type: ModelType,
80 n_params: u32,
81 n_features: u32,
82 n_outputs: u32,
83 ) -> Self {
84 Self {
85 model_type,
86 n_params,
87 n_features,
88 n_outputs,
89 params: None,
90 bias: None,
91 extra: None,
92 }
93 }
94
95 #[must_use]
97 pub fn with_params(mut self, params: AlignedVec<f32>) -> Self {
98 self.params = Some(params);
99 self
100 }
101
102 #[must_use]
104 pub fn with_bias(mut self, bias: AlignedVec<f32>) -> Self {
105 self.bias = Some(bias);
106 self
107 }
108
109 #[must_use]
111 pub fn with_extra(mut self, extra: ModelExtra) -> Self {
112 self.extra = Some(extra);
113 self
114 }
115
116 #[must_use]
118 pub fn is_aligned(&self) -> bool {
119 let params_aligned = self.params.as_ref().map_or(true, AlignedVec::is_aligned);
120 let bias_aligned = self.bias.as_ref().map_or(true, AlignedVec::is_aligned);
121 params_aligned && bias_aligned
122 }
123
124 #[must_use]
126 pub fn size_bytes(&self) -> usize {
127 let params_size = self.params.as_ref().map_or(0, AlignedVec::size_bytes);
128 let bias_size = self.bias.as_ref().map_or(0, AlignedVec::size_bytes);
129 let extra_size = self.extra.as_ref().map_or(0, ModelExtra::size_bytes);
130 params_size + bias_size + extra_size
131 }
132
133 pub fn validate(&self) -> Result<(), NativeModelError> {
135 if let Some(ref params) = self.params {
137 if params.len() != self.n_params as usize {
138 return Err(NativeModelError::ParamCountMismatch {
139 declared: self.n_params as usize,
140 actual: params.len(),
141 });
142 }
143 }
144
145 if let Some(ref params) = self.params {
147 for (i, &val) in params.as_slice().iter().enumerate() {
148 if !val.is_finite() {
149 return Err(NativeModelError::InvalidParameter {
150 index: i,
151 value: val,
152 });
153 }
154 }
155 }
156
157 if let Some(ref bias) = self.bias {
159 for (i, &val) in bias.as_slice().iter().enumerate() {
160 if !val.is_finite() {
161 return Err(NativeModelError::InvalidBias {
162 index: i,
163 value: val,
164 });
165 }
166 }
167 }
168
169 Ok(())
170 }
171
172 #[must_use]
177 pub fn params_ptr(&self) -> Option<*const f32> {
178 self.params.as_ref().map(AlignedVec::as_ptr)
179 }
180
181 #[must_use]
186 pub fn bias_ptr(&self) -> Option<*const f32> {
187 self.bias.as_ref().map(AlignedVec::as_ptr)
188 }
189
190 pub fn predict_linear(&self, features: &[f32]) -> Result<f32, NativeModelError> {
195 if features.len() != self.n_features as usize {
196 return Err(NativeModelError::FeatureMismatch {
197 expected: self.n_features as usize,
198 got: features.len(),
199 });
200 }
201
202 let params = self
203 .params
204 .as_ref()
205 .ok_or(NativeModelError::MissingParams)?;
206
207 let dot: f32 = params
208 .as_slice()
209 .iter()
210 .zip(features.iter())
211 .map(|(p, x)| p * x)
212 .sum();
213
214 let bias = self
215 .bias
216 .as_ref()
217 .and_then(|b| b.as_slice().first().copied())
218 .unwrap_or(0.0);
219
220 Ok(dot + bias)
221 }
222}
223
224impl Default for TruenoNativeModel {
225 fn default() -> Self {
226 Self::new(ModelType::LinearRegression, 0, 0, 1)
227 }
228}
229
230#[derive(Debug, Clone)]
252pub struct AlignedVec<T: Copy + Default> {
253 data: Vec<T>,
255 len: usize,
257 capacity: usize,
259}
260
261impl<T: Copy + Default> AlignedVec<T> {
262 #[must_use]
264 pub fn with_capacity(capacity: usize) -> Self {
265 let size_of_t = size_of::<T>();
266 let aligned_cap = if size_of_t > 0 {
267 (capacity * size_of_t + 63) / 64 * 64 / size_of_t
268 } else {
269 capacity
270 };
271 let aligned_cap = aligned_cap.max(capacity);
272 let data = vec![T::default(); aligned_cap];
273 Self {
274 data,
275 len: 0,
276 capacity: aligned_cap,
277 }
278 }
279
280 #[must_use]
282 pub fn from_slice(slice: &[T]) -> Self {
283 let mut vec = Self::with_capacity(slice.len());
284 vec.data[..slice.len()].copy_from_slice(slice);
285 vec.len = slice.len();
286 vec
287 }
288
289 #[must_use]
291 pub fn zeros(len: usize) -> Self {
292 let mut vec = Self::with_capacity(len);
293 vec.len = len;
294 vec
295 }
296
297 #[must_use]
299 pub const fn len(&self) -> usize {
300 self.len
301 }
302
303 #[must_use]
305 pub const fn is_empty(&self) -> bool {
306 self.len == 0
307 }
308
309 #[must_use]
311 pub const fn capacity(&self) -> usize {
312 self.capacity
313 }
314
315 #[must_use]
317 pub fn as_ptr(&self) -> *const T {
318 self.data.as_ptr()
319 }
320
321 #[must_use]
323 pub fn as_mut_ptr(&mut self) -> *mut T {
324 self.data.as_mut_ptr()
325 }
326
327 #[must_use]
329 pub fn as_slice(&self) -> &[T] {
330 &self.data[..self.len]
331 }
332
333 pub fn as_mut_slice(&mut self) -> &mut [T] {
335 &mut self.data[..self.len]
336 }
337
338 #[must_use]
344 pub fn is_aligned(&self) -> bool {
345 if self.data.is_empty() || size_of::<T>() == 0 {
349 return true;
350 }
351 self.data.as_ptr() as usize % align_of::<T>() == 0
353 }
354
355 #[must_use]
357 pub fn size_bytes(&self) -> usize {
358 self.len * size_of::<T>()
359 }
360
361 pub fn push(&mut self, value: T) {
363 if self.len >= self.data.len() {
364 let new_cap = (self.capacity * 2).max(16);
366 let mut new_data = vec![T::default(); new_cap];
367 new_data[..self.len].copy_from_slice(&self.data[..self.len]);
368 self.data = new_data;
369 self.capacity = new_cap;
370 }
371 self.data[self.len] = value;
372 self.len += 1;
373 }
374
375 pub fn clear(&mut self) {
377 self.len = 0;
378 }
379
380 #[must_use]
382 pub fn get(&self, index: usize) -> Option<&T> {
383 if index < self.len {
384 Some(&self.data[index])
385 } else {
386 None
387 }
388 }
389
390 pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
392 if index < self.len {
393 Some(&mut self.data[index])
394 } else {
395 None
396 }
397 }
398
399 pub fn set(&mut self, index: usize, value: T) -> bool {
401 if index < self.len {
402 self.data[index] = value;
403 true
404 } else {
405 false
406 }
407 }
408}
409
410impl<T: Copy + Default> Default for AlignedVec<T> {
411 fn default() -> Self {
412 Self::with_capacity(0)
413 }
414}
415
416impl<T: Copy + Default> std::ops::Index<usize> for AlignedVec<T> {
417 type Output = T;
418
419 fn index(&self, index: usize) -> &Self::Output {
420 &self.data[index]
421 }
422}
423
424impl<T: Copy + Default> std::ops::IndexMut<usize> for AlignedVec<T> {
425 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
426 &mut self.data[index]
427 }
428}
429
430impl<T: Copy + Default> FromIterator<T> for AlignedVec<T> {
431 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
432 let vec: Vec<T> = iter.into_iter().collect();
433 Self::from_slice(&vec)
434 }
435}
436
437impl<T: Copy + Default + PartialEq> PartialEq for AlignedVec<T> {
438 fn eq(&self, other: &Self) -> bool {
439 self.as_slice() == other.as_slice()
440 }
441}
442
443#[derive(Debug, Clone, Default)]
445pub struct ModelExtra {
446 pub tree_data: Option<TreeData>,
448
449 pub layer_data: Option<Vec<LayerData>>,
451
452 pub centroids: Option<AlignedVec<f32>>,
454
455 pub metadata: std::collections::HashMap<String, Vec<u8>>,
457}
458
459impl ModelExtra {
460 #[must_use]
462 pub fn new() -> Self {
463 Self::default()
464 }
465
466 #[must_use]
468 pub fn with_tree(mut self, tree: TreeData) -> Self {
469 self.tree_data = Some(tree);
470 self
471 }
472
473 #[must_use]
475 pub fn with_layers(mut self, layers: Vec<LayerData>) -> Self {
476 self.layer_data = Some(layers);
477 self
478 }
479
480 #[must_use]
482 pub fn with_centroids(mut self, centroids: AlignedVec<f32>) -> Self {
483 self.centroids = Some(centroids);
484 self
485 }
486
487 #[must_use]
489 pub fn with_metadata(mut self, key: impl Into<String>, value: Vec<u8>) -> Self {
490 self.metadata.insert(key.into(), value);
491 self
492 }
493
494 #[must_use]
496 pub fn size_bytes(&self) -> usize {
497 let tree_size = self.tree_data.as_ref().map_or(0, TreeData::size_bytes);
498 let layer_size: usize = self
499 .layer_data
500 .as_ref()
501 .map_or(0, |layers| layers.iter().map(LayerData::size_bytes).sum());
502 let centroid_size = self.centroids.as_ref().map_or(0, AlignedVec::size_bytes);
503 let metadata_size: usize = self.metadata.values().map(Vec::len).sum();
504 tree_size + layer_size + centroid_size + metadata_size
505 }
506}
507
508#[derive(Debug, Clone)]
510pub struct TreeData {
511 pub feature_indices: Vec<u16>,
513 pub thresholds: Vec<f32>,
515 pub left_children: Vec<i32>,
517 pub right_children: Vec<i32>,
519 pub leaf_values: Vec<f32>,
521}
522
523impl TreeData {
524 #[must_use]
526 pub fn new() -> Self {
527 Self {
528 feature_indices: Vec::new(),
529 thresholds: Vec::new(),
530 left_children: Vec::new(),
531 right_children: Vec::new(),
532 leaf_values: Vec::new(),
533 }
534 }
535
536 #[must_use]
538 pub fn n_nodes(&self) -> usize {
539 self.thresholds.len()
540 }
541
542 #[must_use]
544 pub fn size_bytes(&self) -> usize {
545 self.feature_indices.len() * 2
546 + self.thresholds.len() * 4
547 + self.left_children.len() * 4
548 + self.right_children.len() * 4
549 + self.leaf_values.len() * 4
550 }
551}
552
553impl Default for TreeData {
554 fn default() -> Self {
555 Self::new()
556 }
557}
558
559#[derive(Debug, Clone)]
561pub struct LayerData {
562 pub layer_type: LayerType,
564 pub input_dim: u32,
566 pub output_dim: u32,
568 pub weights: Option<AlignedVec<f32>>,
570 pub biases: Option<AlignedVec<f32>>,
572}
573
574impl LayerData {
575 #[must_use]
577 pub fn dense(input_dim: u32, output_dim: u32) -> Self {
578 Self {
579 layer_type: LayerType::Dense,
580 input_dim,
581 output_dim,
582 weights: None,
583 biases: None,
584 }
585 }
586
587 #[must_use]
589 pub fn with_weights(mut self, weights: AlignedVec<f32>) -> Self {
590 self.weights = Some(weights);
591 self
592 }
593
594 #[must_use]
596 pub fn with_biases(mut self, biases: AlignedVec<f32>) -> Self {
597 self.biases = Some(biases);
598 self
599 }
600
601 #[must_use]
603 pub fn size_bytes(&self) -> usize {
604 let weights_size = self.weights.as_ref().map_or(0, AlignedVec::size_bytes);
605 let biases_size = self.biases.as_ref().map_or(0, AlignedVec::size_bytes);
606 weights_size + biases_size + 12 }
608}
609
610#[derive(Debug, Clone, Copy, PartialEq, Eq)]
612pub enum LayerType {
613 Dense,
615 ReLU,
617 Sigmoid,
619 Tanh,
621 Softmax,
623 Dropout,
625 BatchNorm,
627}
628
629#[derive(Debug, Clone)]
631pub enum NativeModelError {
632 ParamCountMismatch { declared: usize, actual: usize },
634 InvalidParameter { index: usize, value: f32 },
636 InvalidBias { index: usize, value: f32 },
638 FeatureMismatch { expected: usize, got: usize },
640 MissingParams,
642 AlignmentError { ptr: usize, required: usize },
644}
645
646impl std::fmt::Display for NativeModelError {
647 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
648 match self {
649 Self::ParamCountMismatch { declared, actual } => {
650 write!(
651 f,
652 "Parameter count mismatch: declared {declared}, actual {actual}"
653 )
654 }
655 Self::InvalidParameter { index, value } => {
656 write!(f, "Invalid parameter at index {index}: {value}")
657 }
658 Self::InvalidBias { index, value } => {
659 write!(f, "Invalid bias at index {index}: {value}")
660 }
661 Self::FeatureMismatch { expected, got } => {
662 write!(f, "Feature mismatch: expected {expected}, got {got}")
663 }
664 Self::MissingParams => write!(f, "Missing model parameters"),
665 Self::AlignmentError { ptr, required } => {
666 write!(
667 f,
668 "Alignment error: ptr 0x{ptr:x} not aligned to {required}"
669 )
670 }
671 }
672 }
673}
674
675impl std::error::Error for NativeModelError {}
676
677#[cfg(test)]
678mod tests {
679 use super::*;
680
681 #[test]
682 fn test_aligned_vec_creation() {
683 let vec = AlignedVec::<f32>::with_capacity(10);
684 assert_eq!(vec.len(), 0);
685 assert!(vec.capacity() >= 10);
686 }
687
688 #[test]
689 fn test_aligned_vec_from_slice() {
690 let vec = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0, 4.0]);
691 assert_eq!(vec.len(), 4);
692 assert_eq!(vec.as_slice(), &[1.0, 2.0, 3.0, 4.0]);
693 }
694
695 #[test]
696 fn test_aligned_vec_zeros() {
697 let vec = AlignedVec::<f32>::zeros(100);
698 assert_eq!(vec.len(), 100);
699 assert!(vec.as_slice().iter().all(|&x| x == 0.0));
700 }
701
702 #[test]
703 fn test_aligned_vec_push() {
704 let mut vec = AlignedVec::<f32>::with_capacity(2);
705 vec.push(1.0);
706 vec.push(2.0);
707 vec.push(3.0); assert_eq!(vec.len(), 3);
710 assert_eq!(vec.as_slice(), &[1.0, 2.0, 3.0]);
711 }
712
713 #[test]
714 fn test_aligned_vec_index() {
715 let vec = AlignedVec::from_slice(&[10.0_f32, 20.0, 30.0]);
716 assert_eq!(vec[0], 10.0);
717 assert_eq!(vec[1], 20.0);
718 assert_eq!(vec[2], 30.0);
719 }
720
721 #[test]
722 fn test_aligned_vec_get() {
723 let vec = AlignedVec::from_slice(&[1.0_f32, 2.0]);
724 assert_eq!(vec.get(0), Some(&1.0));
725 assert_eq!(vec.get(1), Some(&2.0));
726 assert_eq!(vec.get(2), None);
727 }
728
729 #[test]
730 fn test_aligned_vec_set() {
731 let mut vec = AlignedVec::from_slice(&[1.0_f32, 2.0]);
732 assert!(vec.set(0, 10.0));
733 assert_eq!(vec[0], 10.0);
734 assert!(!vec.set(5, 50.0)); }
736
737 #[test]
738 fn test_aligned_vec_clear() {
739 let mut vec = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]);
740 vec.clear();
741 assert!(vec.is_empty());
742 assert!(vec.capacity() >= 3);
743 }
744
745 #[test]
746 fn test_aligned_vec_from_iterator() {
747 let vec: AlignedVec<f32> = (0..5).map(|i| i as f32).collect();
748 assert_eq!(vec.len(), 5);
749 assert_eq!(vec.as_slice(), &[0.0, 1.0, 2.0, 3.0, 4.0]);
750 }
751
752 #[test]
753 fn test_aligned_vec_eq() {
754 let a = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]);
755 let b = AlignedVec::from_slice(&[1.0, 2.0, 3.0]);
756 let c = AlignedVec::from_slice(&[1.0, 2.0, 4.0]);
757
758 assert_eq!(a, b);
759 assert_ne!(a, c);
760 }
761
762 #[test]
763 fn test_trueno_native_model_creation() {
764 let model = TruenoNativeModel::new(ModelType::LinearRegression, 10, 10, 1);
765
766 assert_eq!(model.n_params, 10);
767 assert_eq!(model.n_features, 10);
768 assert_eq!(model.n_outputs, 1);
769 }
770
771 #[test]
772 fn test_trueno_native_model_with_params() {
773 let params = AlignedVec::from_slice(&[0.5_f32, -0.3, 0.8]);
774 let bias = AlignedVec::from_slice(&[1.0_f32]);
775
776 let model = TruenoNativeModel::new(ModelType::LinearRegression, 3, 3, 1)
777 .with_params(params)
778 .with_bias(bias);
779
780 assert!(model.params.is_some());
781 assert!(model.bias.is_some());
782 assert!(model.is_aligned());
783 }
784
785 #[test]
786 fn test_trueno_native_model_validate() {
787 let params = AlignedVec::from_slice(&[0.5_f32, -0.3, 0.8]);
788 let model =
789 TruenoNativeModel::new(ModelType::LinearRegression, 3, 3, 1).with_params(params);
790
791 assert!(model.validate().is_ok());
792 }
793
794 #[test]
795 fn test_trueno_native_model_validate_param_mismatch() {
796 let params = AlignedVec::from_slice(&[0.5_f32, -0.3]); let model = TruenoNativeModel::new(ModelType::LinearRegression, 3, 3, 1) .with_params(params);
799
800 assert!(matches!(
801 model.validate(),
802 Err(NativeModelError::ParamCountMismatch { .. })
803 ));
804 }
805
806 #[test]
807 fn test_trueno_native_model_validate_nan() {
808 let params = AlignedVec::from_slice(&[0.5_f32, f32::NAN, 0.8]);
809 let model =
810 TruenoNativeModel::new(ModelType::LinearRegression, 3, 3, 1).with_params(params);
811
812 assert!(matches!(
813 model.validate(),
814 Err(NativeModelError::InvalidParameter { index: 1, .. })
815 ));
816 }
817
818 #[test]
819 fn test_trueno_native_model_predict_linear() {
820 let params = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]);
821 let bias = AlignedVec::from_slice(&[1.0_f32]);
822
823 let model = TruenoNativeModel::new(ModelType::LinearRegression, 3, 3, 1)
824 .with_params(params)
825 .with_bias(bias);
826
827 let pred = model.predict_linear(&[1.0, 2.0, 3.0]).unwrap();
829 assert!((pred - 15.0).abs() < f32::EPSILON);
830 }
831
832 #[test]
833 fn test_trueno_native_model_predict_linear_feature_mismatch() {
834 let params = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]);
835 let model =
836 TruenoNativeModel::new(ModelType::LinearRegression, 3, 3, 1).with_params(params);
837
838 let result = model.predict_linear(&[1.0, 2.0]); assert!(matches!(
840 result,
841 Err(NativeModelError::FeatureMismatch {
842 expected: 3,
843 got: 2
844 })
845 ));
846 }
847
848 #[test]
849 fn test_trueno_native_model_predict_linear_missing_params() {
850 let model = TruenoNativeModel::new(ModelType::LinearRegression, 3, 3, 1);
851
852 let result = model.predict_linear(&[1.0, 2.0, 3.0]);
853 assert!(matches!(result, Err(NativeModelError::MissingParams)));
854 }
855
856 #[test]
857 fn test_model_extra() {
858 let extra = ModelExtra::new()
859 .with_centroids(AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]))
860 .with_metadata("key", vec![1, 2, 3]);
861
862 assert!(extra.centroids.is_some());
863 assert_eq!(extra.metadata.get("key"), Some(&vec![1, 2, 3]));
864 assert!(extra.size_bytes() > 0);
865 }
866
867 #[test]
868 fn test_tree_data() {
869 let tree = TreeData {
870 feature_indices: vec![0, 1],
871 thresholds: vec![0.5, 0.3],
872 left_children: vec![1, -1],
873 right_children: vec![2, -1],
874 leaf_values: vec![0.0, 1.0, 0.5],
875 };
876
877 assert_eq!(tree.n_nodes(), 2);
878 assert!(tree.size_bytes() > 0);
879 }
880
881 #[test]
882 fn test_layer_data() {
883 let layer = LayerData::dense(100, 50)
884 .with_weights(AlignedVec::zeros(5000))
885 .with_biases(AlignedVec::zeros(50));
886
887 assert_eq!(layer.input_dim, 100);
888 assert_eq!(layer.output_dim, 50);
889 assert!(layer.size_bytes() > 0);
890 }
891
892 #[test]
893 fn test_native_model_error_display() {
894 let err = NativeModelError::ParamCountMismatch {
895 declared: 10,
896 actual: 5,
897 };
898 let msg = format!("{err}");
899 assert!(msg.contains("10"));
900 assert!(msg.contains("5"));
901
902 let err = NativeModelError::MissingParams;
903 assert_eq!(format!("{err}"), "Missing model parameters");
904 }
905
906 #[test]
907 fn test_trueno_native_model_size_bytes() {
908 let params = AlignedVec::from_slice(&[1.0_f32; 100]);
909 let bias = AlignedVec::from_slice(&[1.0_f32; 10]);
910
911 let model = TruenoNativeModel::new(ModelType::LinearRegression, 100, 100, 10)
912 .with_params(params)
913 .with_bias(bias);
914
915 assert_eq!(model.size_bytes(), 440);
917 }
918
919 #[test]
920 fn test_trueno_native_model_default() {
921 let model = TruenoNativeModel::default();
922 assert_eq!(model.n_params, 0);
923 assert_eq!(model.n_features, 0);
924 assert_eq!(model.n_outputs, 1);
925 }
926
927 #[test]
928 fn test_aligned_vec_default() {
929 let vec = AlignedVec::<f32>::default();
930 assert!(vec.is_empty());
931 }
932
933 #[test]
938 fn test_aligned_vec_as_mut_ptr() {
939 let mut vec = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]);
940 let ptr = vec.as_mut_ptr();
941 assert!(!ptr.is_null());
942 }
943
944 #[test]
945 fn test_aligned_vec_as_mut_slice() {
946 let mut vec = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]);
947 let slice = vec.as_mut_slice();
948 slice[0] = 10.0;
949 assert_eq!(vec[0], 10.0);
950 }
951
952 #[test]
953 fn test_aligned_vec_get_mut() {
954 let mut vec = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]);
955 if let Some(val) = vec.get_mut(1) {
956 *val = 20.0;
957 }
958 assert_eq!(vec[1], 20.0);
959 assert!(vec.get_mut(10).is_none());
960 }
961
962 #[test]
963 fn test_aligned_vec_index_mut() {
964 let mut vec = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]);
965 vec[0] = 100.0;
966 assert_eq!(vec[0], 100.0);
967 }
968
969 #[test]
970 fn test_aligned_vec_is_aligned_empty() {
971 let vec = AlignedVec::<f32>::with_capacity(0);
972 assert!(vec.is_aligned());
973 }
974
975 #[test]
976 fn test_trueno_native_model_with_extra() {
977 let extra = ModelExtra::new();
978 let model = TruenoNativeModel::new(ModelType::LinearRegression, 0, 0, 1).with_extra(extra);
979 assert!(model.extra.is_some());
980 }
981
982 #[test]
983 fn test_trueno_native_model_params_ptr() {
984 let params = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]);
985 let model =
986 TruenoNativeModel::new(ModelType::LinearRegression, 3, 3, 1).with_params(params);
987
988 let ptr = model.params_ptr();
989 assert!(ptr.is_some());
990 assert!(!ptr.unwrap().is_null());
991 }
992
993 #[test]
994 fn test_trueno_native_model_params_ptr_none() {
995 let model = TruenoNativeModel::new(ModelType::LinearRegression, 0, 0, 1);
996 assert!(model.params_ptr().is_none());
997 }
998
999 #[test]
1000 fn test_trueno_native_model_bias_ptr() {
1001 let bias = AlignedVec::from_slice(&[1.0_f32]);
1002 let model = TruenoNativeModel::new(ModelType::LinearRegression, 0, 0, 1).with_bias(bias);
1003
1004 let ptr = model.bias_ptr();
1005 assert!(ptr.is_some());
1006 }
1007
1008 #[test]
1009 fn test_trueno_native_model_bias_ptr_none() {
1010 let model = TruenoNativeModel::new(ModelType::LinearRegression, 0, 0, 1);
1011 assert!(model.bias_ptr().is_none());
1012 }
1013
1014 #[test]
1015 fn test_trueno_native_model_validate_invalid_bias() {
1016 let params = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]);
1017 let bias = AlignedVec::from_slice(&[f32::INFINITY]);
1018 let model = TruenoNativeModel::new(ModelType::LinearRegression, 3, 3, 1)
1019 .with_params(params)
1020 .with_bias(bias);
1021
1022 let result = model.validate();
1023 assert!(matches!(
1024 result,
1025 Err(NativeModelError::InvalidBias { index: 0, .. })
1026 ));
1027 }
1028
1029 #[test]
1030 fn test_model_extra_with_tree() {
1031 let tree = TreeData::new();
1032 let extra = ModelExtra::new().with_tree(tree);
1033 assert!(extra.tree_data.is_some());
1034 }
1035
1036 #[test]
1037 fn test_model_extra_with_layers() {
1038 let layers = vec![LayerData::dense(10, 5)];
1039 let extra = ModelExtra::new().with_layers(layers);
1040 assert!(extra.layer_data.is_some());
1041 }
1042
1043 #[test]
1044 fn test_tree_data_new_and_default() {
1045 let tree1 = TreeData::new();
1046 let tree2 = TreeData::default();
1047 assert_eq!(tree1.n_nodes(), 0);
1048 assert_eq!(tree2.n_nodes(), 0);
1049 }
1050
1051 #[test]
1052 fn test_layer_type_all_variants() {
1053 let types = [
1054 LayerType::Dense,
1055 LayerType::ReLU,
1056 LayerType::Sigmoid,
1057 LayerType::Tanh,
1058 LayerType::Softmax,
1059 LayerType::Dropout,
1060 LayerType::BatchNorm,
1061 ];
1062 for lt in &types {
1063 let debug = format!("{:?}", lt);
1064 assert!(!debug.is_empty());
1065 }
1066 }
1067
1068 #[test]
1069 fn test_layer_type_eq() {
1070 assert_eq!(LayerType::Dense, LayerType::Dense);
1071 assert_ne!(LayerType::Dense, LayerType::ReLU);
1072 }
1073
1074 #[test]
1075 fn test_native_model_error_display_all_variants() {
1076 let errors = [
1077 NativeModelError::ParamCountMismatch {
1078 declared: 10,
1079 actual: 5,
1080 },
1081 NativeModelError::InvalidParameter {
1082 index: 0,
1083 value: f32::NAN,
1084 },
1085 NativeModelError::InvalidBias {
1086 index: 0,
1087 value: f32::INFINITY,
1088 },
1089 NativeModelError::FeatureMismatch {
1090 expected: 3,
1091 got: 2,
1092 },
1093 NativeModelError::MissingParams,
1094 NativeModelError::AlignmentError {
1095 ptr: 12345,
1096 required: 64,
1097 },
1098 ];
1099
1100 for err in &errors {
1101 let msg = format!("{err}");
1102 assert!(!msg.is_empty());
1103 }
1104 }
1105
1106 #[test]
1107 fn test_native_model_error_debug_clone() {
1108 let err = NativeModelError::MissingParams;
1109 let cloned = err.clone();
1110 let debug = format!("{:?}", cloned);
1111 assert!(debug.contains("MissingParams"));
1112 }
1113
1114 #[test]
1115 fn test_native_model_error_is_error() {
1116 let err = NativeModelError::MissingParams;
1117 let _: &dyn std::error::Error = &err;
1118 }
1119
1120 #[test]
1121 fn test_trueno_native_model_debug_clone() {
1122 let model = TruenoNativeModel::default();
1123 let cloned = model.clone();
1124 assert_eq!(model.n_params, cloned.n_params);
1125
1126 let debug = format!("{:?}", model);
1127 assert!(debug.contains("TruenoNativeModel"));
1128 }
1129
1130 #[test]
1131 fn test_aligned_vec_debug_clone() {
1132 let vec = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]);
1133 let cloned = vec.clone();
1134 assert_eq!(vec, cloned);
1135
1136 let debug = format!("{:?}", vec);
1137 assert!(debug.contains("AlignedVec"));
1138 }
1139
1140 #[test]
1141 fn test_model_extra_debug_clone_default() {
1142 let extra = ModelExtra::default();
1143 let cloned = extra.clone();
1144 let debug = format!("{:?}", cloned);
1145 assert!(debug.contains("ModelExtra"));
1146 }
1147
1148 #[test]
1149 fn test_tree_data_debug_clone() {
1150 let tree = TreeData::default();
1151 let cloned = tree.clone();
1152 let debug = format!("{:?}", cloned);
1153 assert!(debug.contains("TreeData"));
1154 }
1155
1156 #[test]
1157 fn test_layer_data_debug_clone() {
1158 let layer = LayerData::dense(10, 5);
1159 let cloned = layer.clone();
1160 assert_eq!(layer.input_dim, cloned.input_dim);
1161
1162 let debug = format!("{:?}", layer);
1163 assert!(debug.contains("LayerData"));
1164 }
1165
1166 #[test]
1167 fn test_aligned_vec_push_triggers_realloc() {
1168 let mut vec = AlignedVec::<f32>::with_capacity(1);
1169 vec.push(1.0);
1170 vec.push(2.0);
1171 vec.push(3.0);
1172 vec.push(4.0);
1173 assert_eq!(vec.len(), 4);
1174 }
1175
1176 #[test]
1177 fn test_model_extra_size_bytes_all_components() {
1178 let tree = TreeData {
1179 feature_indices: vec![0, 1],
1180 thresholds: vec![0.5, 0.3],
1181 left_children: vec![1, -1],
1182 right_children: vec![2, -1],
1183 leaf_values: vec![0.0, 1.0, 0.5],
1184 };
1185 let layer = LayerData::dense(10, 5)
1186 .with_weights(AlignedVec::zeros(50))
1187 .with_biases(AlignedVec::zeros(5));
1188 let extra = ModelExtra::new()
1189 .with_tree(tree)
1190 .with_layers(vec![layer])
1191 .with_centroids(AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]))
1192 .with_metadata("key", vec![1, 2, 3, 4, 5]);
1193
1194 assert!(extra.size_bytes() > 0);
1195 }
1196
1197 #[test]
1198 fn test_trueno_native_model_predict_linear_no_bias() {
1199 let params = AlignedVec::from_slice(&[1.0_f32, 2.0, 3.0]);
1200 let model =
1201 TruenoNativeModel::new(ModelType::LinearRegression, 3, 3, 1).with_params(params);
1202
1203 let pred = model.predict_linear(&[1.0, 2.0, 3.0]).unwrap();
1205 assert!((pred - 14.0).abs() < f32::EPSILON);
1206 }
1207}