1use std::any::TypeId;
15use std::fmt;
16use std::marker::PhantomData;
17
18use crate::error::{CoreError, CoreResult, ErrorContext, ErrorLocation};
19use num_complex::Complex;
20use num_traits::{Float, NumCast, Zero};
21
22#[derive(Debug, Clone, PartialEq)]
24pub enum ConversionError {
25 TypeMismatch { expected: String, actual: String },
27 OutOfRange {
29 value: String,
30 min: String,
31 max: String,
32 },
33 PrecisionLoss {
35 original: String,
36 converted: String,
37 loss: f64,
38 },
39 ShapeMismatch {
41 source: Vec<usize>,
42 target: Vec<usize>,
43 },
44 InvalidOperation(String),
46 Failed(String),
48}
49
50impl fmt::Display for ConversionError {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self {
53 ConversionError::TypeMismatch { expected, actual } => {
54 write!(f, "Type mismatch: expected {expected}, got {actual}")
55 }
56 ConversionError::OutOfRange { value, min, max } => {
57 write!(f, "Value {value} out of range [{min}, {max}]")
58 }
59 ConversionError::PrecisionLoss {
60 original,
61 converted,
62 loss,
63 } => {
64 write!(
65 f,
66 "Precision loss: {original} -> {converted} (loss: {loss:.2e})"
67 )
68 }
69 ConversionError::ShapeMismatch { source, target } => {
70 write!(f, "Shape mismatch: {source:?} vs {target:?}")
71 }
72 ConversionError::InvalidOperation(msg) => {
73 write!(f, "Invalid operation: {msg}")
74 }
75 ConversionError::Failed(msg) => {
76 write!(f, "Conversion failed: {msg}")
77 }
78 }
79 }
80}
81
82impl std::error::Error for ConversionError {}
83
84impl From<ConversionError> for CoreError {
85 fn from(err: ConversionError) -> Self {
86 CoreError::ValidationError(
87 ErrorContext::new(err.to_string()).with_location(ErrorLocation::new(file!(), line!())),
88 )
89 }
90}
91
92pub type ConversionResult<T> = Result<T, ConversionError>;
94
95#[derive(Debug, Clone, PartialEq)]
97pub struct ConversionOptions {
98 pub allow_precision_loss: bool,
100 pub max_relative_error: f64,
102 pub clamp_to_range: bool,
104 pub round_to_nearest: bool,
106 pub preserve_nan: bool,
108 pub preserve_infinity: bool,
110}
111
112impl Default for ConversionOptions {
113 fn default() -> Self {
114 Self {
115 allow_precision_loss: true,
116 max_relative_error: 1e-10,
117 clamp_to_range: false,
118 round_to_nearest: true,
119 preserve_nan: true,
120 preserve_infinity: true,
121 }
122 }
123}
124
125impl ConversionOptions {
126 #[must_use]
128 pub fn strict() -> Self {
129 Self {
130 allow_precision_loss: false,
131 max_relative_error: 0.0,
132 clamp_to_range: false,
133 round_to_nearest: false,
134 preserve_nan: true,
135 preserve_infinity: true,
136 }
137 }
138
139 #[must_use]
141 pub fn lenient() -> Self {
142 Self {
143 allow_precision_loss: true,
144 max_relative_error: 1e-6,
145 clamp_to_range: true,
146 round_to_nearest: true,
147 preserve_nan: true,
148 preserve_infinity: true,
149 }
150 }
151}
152
153pub trait LosslessConvert<T> {
155 fn convert_lossless(&self) -> ConversionResult<T>;
157}
158
159pub trait LossyConvert<T> {
161 fn convert_lossy(&self) -> T;
163
164 fn convert_with_options(&self, options: &ConversionOptions) -> ConversionResult<T>;
166}
167
168pub trait CrossModuleConvert {
170 type Source;
172 type Target;
174
175 fn convert(source: &Self::Source) -> ConversionResult<Self::Target>;
177
178 fn convert_with_options(
180 source: &Self::Source,
181 options: &ConversionOptions,
182 ) -> ConversionResult<Self::Target>;
183
184 fn can_convert(source: &Self::Source) -> bool;
186}
187
188pub trait ArrayConvert<T> {
190 type Output;
192
193 fn convert_array(&self) -> ConversionResult<Self::Output>;
195
196 fn convert_array_with_options(
198 &self,
199 options: &ConversionOptions,
200 ) -> ConversionResult<Self::Output>;
201}
202
203impl LosslessConvert<f64> for f32 {
205 fn convert_lossless(&self) -> ConversionResult<f64> {
206 Ok(*self as f64)
207 }
208}
209
210impl LosslessConvert<i64> for i32 {
211 fn convert_lossless(&self) -> ConversionResult<i64> {
212 Ok(*self as i64)
213 }
214}
215
216impl LosslessConvert<i64> for i16 {
217 fn convert_lossless(&self) -> ConversionResult<i64> {
218 Ok(*self as i64)
219 }
220}
221
222impl LosslessConvert<i64> for i8 {
223 fn convert_lossless(&self) -> ConversionResult<i64> {
224 Ok(*self as i64)
225 }
226}
227
228impl LosslessConvert<u64> for u32 {
229 fn convert_lossless(&self) -> ConversionResult<u64> {
230 Ok(*self as u64)
231 }
232}
233
234impl LosslessConvert<u64> for u16 {
235 fn convert_lossless(&self) -> ConversionResult<u64> {
236 Ok(*self as u64)
237 }
238}
239
240impl LosslessConvert<u64> for u8 {
241 fn convert_lossless(&self) -> ConversionResult<u64> {
242 Ok(*self as u64)
243 }
244}
245
246impl<T: Float> LosslessConvert<Complex<T>> for T {
247 fn convert_lossless(&self) -> ConversionResult<Complex<T>> {
248 Ok(Complex::new(*self, T::zero()))
249 }
250}
251
252impl LossyConvert<f32> for f64 {
254 fn convert_lossy(&self) -> f32 {
255 *self as f32
256 }
257
258 fn convert_with_options(&self, options: &ConversionOptions) -> ConversionResult<f32> {
259 let converted = *self as f32;
260
261 if !options.allow_precision_loss {
262 let back = converted as f64;
264 let relative_error = if self.abs() > f64::EPSILON {
265 ((back - *self) / *self).abs()
266 } else {
267 (back - *self).abs()
268 };
269
270 if relative_error > options.max_relative_error {
271 return Err(ConversionError::PrecisionLoss {
272 original: format!("{self}"),
273 converted: format!("{converted}"),
274 loss: relative_error,
275 });
276 }
277 }
278
279 if self.is_infinite() && !options.preserve_infinity {
280 return Err(ConversionError::OutOfRange {
281 value: format!("{self}"),
282 min: format!("{}", f32::MIN),
283 max: format!("{}", f32::MAX),
284 });
285 }
286
287 if self.is_nan() && !options.preserve_nan {
288 return Err(ConversionError::InvalidOperation(
289 "Cannot convert NaN".to_string(),
290 ));
291 }
292
293 Ok(converted)
294 }
295}
296
297impl LossyConvert<i32> for f64 {
298 fn convert_lossy(&self) -> i32 {
299 *self as i32
300 }
301
302 fn convert_with_options(&self, options: &ConversionOptions) -> ConversionResult<i32> {
303 if self.is_nan() {
304 return Err(ConversionError::InvalidOperation(
305 "Cannot convert NaN to integer".to_string(),
306 ));
307 }
308
309 if self.is_infinite() {
310 if options.clamp_to_range {
311 return Ok(if *self > 0.0 { i32::MAX } else { i32::MIN });
312 }
313 return Err(ConversionError::OutOfRange {
314 value: format!("{self}"),
315 min: format!("{}", i32::MIN),
316 max: format!("{}", i32::MAX),
317 });
318 }
319
320 let value = if options.round_to_nearest {
321 self.round()
322 } else {
323 self.trunc()
324 };
325
326 if value < i32::MIN as f64 || value > i32::MAX as f64 {
327 if options.clamp_to_range {
328 return Ok(if value < 0.0 { i32::MIN } else { i32::MAX });
329 }
330 return Err(ConversionError::OutOfRange {
331 value: format!("{self}"),
332 min: format!("{}", i32::MIN),
333 max: format!("{}", i32::MAX),
334 });
335 }
336
337 if !options.allow_precision_loss && (value - *self).abs() > f64::EPSILON {
338 return Err(ConversionError::PrecisionLoss {
339 original: format!("{self}"),
340 converted: format!("{}", value as i32),
341 loss: (value - *self).abs(),
342 });
343 }
344
345 Ok(value as i32)
346 }
347}
348
349impl LossyConvert<i64> for f64 {
350 fn convert_lossy(&self) -> i64 {
351 *self as i64
352 }
353
354 fn convert_with_options(&self, options: &ConversionOptions) -> ConversionResult<i64> {
355 if self.is_nan() {
356 return Err(ConversionError::InvalidOperation(
357 "Cannot convert NaN to integer".to_string(),
358 ));
359 }
360
361 if self.is_infinite() {
362 if options.clamp_to_range {
363 return Ok(if *self > 0.0 { i64::MAX } else { i64::MIN });
364 }
365 return Err(ConversionError::OutOfRange {
366 value: format!("{self}"),
367 min: format!("{}", i64::MIN),
368 max: format!("{}", i64::MAX),
369 });
370 }
371
372 let value = if options.round_to_nearest {
373 self.round()
374 } else {
375 self.trunc()
376 };
377
378 if value < i64::MIN as f64 || value > i64::MAX as f64 {
379 if options.clamp_to_range {
380 return Ok(if value < 0.0 { i64::MIN } else { i64::MAX });
381 }
382 return Err(ConversionError::OutOfRange {
383 value: format!("{self}"),
384 min: format!("{}", i64::MIN),
385 max: format!("{}", i64::MAX),
386 });
387 }
388
389 if !options.allow_precision_loss && (value - *self).abs() > f64::EPSILON {
390 return Err(ConversionError::PrecisionLoss {
391 original: format!("{self}"),
392 converted: format!("{}", value as i64),
393 loss: (value - *self).abs(),
394 });
395 }
396
397 Ok(value as i64)
398 }
399}
400
401#[derive(Debug, Clone)]
403pub struct TypeAdapter<S, T> {
404 _source: PhantomData<S>,
405 _target: PhantomData<T>,
406}
407
408impl<S, T> TypeAdapter<S, T> {
409 #[must_use]
411 pub const fn new() -> Self {
412 Self {
413 _source: PhantomData,
414 _target: PhantomData,
415 }
416 }
417}
418
419impl<S, T> Default for TypeAdapter<S, T> {
420 fn default() -> Self {
421 Self::new()
422 }
423}
424
425impl<S, T> TypeAdapter<S, T>
426where
427 S: NumCast + Copy,
428 T: NumCast + Copy,
429{
430 pub fn adapt(&self, value: S) -> ConversionResult<T> {
432 NumCast::from(value).ok_or_else(|| {
433 ConversionError::Failed(format!(
434 "Cannot convert {} to {}",
435 std::any::type_name::<S>(),
436 std::any::type_name::<T>()
437 ))
438 })
439 }
440
441 pub fn adapt_slice(&self, values: &[S]) -> ConversionResult<Vec<T>> {
443 values.iter().map(|&v| self.adapt(v)).collect()
444 }
445}
446
447pub struct DataFlowConverter {
449 options: ConversionOptions,
451 custom_conversions: Vec<(
453 TypeId,
454 TypeId,
455 Box<dyn Fn(&[u8]) -> ConversionResult<Vec<u8>> + Send + Sync>,
456 )>,
457}
458
459impl std::fmt::Debug for DataFlowConverter {
460 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
461 f.debug_struct("DataFlowConverter")
462 .field("options", &self.options)
463 .field("custom_conversions_count", &self.custom_conversions.len())
464 .finish()
465 }
466}
467
468impl Default for DataFlowConverter {
469 fn default() -> Self {
470 Self::new()
471 }
472}
473
474impl DataFlowConverter {
475 #[must_use]
477 pub fn new() -> Self {
478 Self {
479 options: ConversionOptions::default(),
480 custom_conversions: Vec::new(),
481 }
482 }
483
484 #[must_use]
486 pub fn with_options(options: ConversionOptions) -> Self {
487 Self {
488 options,
489 custom_conversions: Vec::new(),
490 }
491 }
492
493 #[must_use]
495 pub const fn options(&self) -> &ConversionOptions {
496 &self.options
497 }
498
499 pub fn set_options(&mut self, options: ConversionOptions) {
501 self.options = options;
502 }
503
504 pub fn convert_slice<S, T>(&self, source: &[S]) -> ConversionResult<Vec<T>>
506 where
507 S: NumCast + Copy,
508 T: NumCast + Copy,
509 {
510 let adapter: TypeAdapter<S, T> = TypeAdapter::new();
511 adapter.adapt_slice(source)
512 }
513
514 pub fn f64_to_f32(&self, values: &[f64]) -> ConversionResult<Vec<f32>> {
516 values
517 .iter()
518 .map(|v| v.convert_with_options(&self.options))
519 .collect()
520 }
521
522 pub fn f64_to_i32(&self, values: &[f64]) -> ConversionResult<Vec<i32>> {
524 values
525 .iter()
526 .map(|v| v.convert_with_options(&self.options))
527 .collect()
528 }
529
530 pub fn f64_to_i64(&self, values: &[f64]) -> ConversionResult<Vec<i64>> {
532 values
533 .iter()
534 .map(|v| v.convert_with_options(&self.options))
535 .collect()
536 }
537
538 pub fn convert<S, T>(&self, source: &[S]) -> ConversionResult<Vec<T>>
540 where
541 S: NumCast + Copy,
542 T: NumCast + Copy,
543 {
544 self.convert_slice(source)
545 }
546}
547
548impl<S, T> ArrayConvert<T> for Vec<S>
550where
551 S: NumCast + Copy,
552 T: NumCast + Copy,
553{
554 type Output = Vec<T>;
555
556 fn convert_array(&self) -> ConversionResult<Self::Output> {
557 let adapter: TypeAdapter<S, T> = TypeAdapter::new();
558 adapter.adapt_slice(self)
559 }
560
561 fn convert_array_with_options(
562 &self,
563 _options: &ConversionOptions,
564 ) -> ConversionResult<Self::Output> {
565 self.convert_array()
568 }
569}
570
571impl<S, T> ArrayConvert<T> for [S]
573where
574 S: NumCast + Copy,
575 T: NumCast + Copy,
576{
577 type Output = Vec<T>;
578
579 fn convert_array(&self) -> ConversionResult<Self::Output> {
580 let adapter: TypeAdapter<S, T> = TypeAdapter::new();
581 adapter.adapt_slice(self)
582 }
583
584 fn convert_array_with_options(
585 &self,
586 _options: &ConversionOptions,
587 ) -> ConversionResult<Self::Output> {
588 self.convert_array()
589 }
590}
591
592pub mod complex {
594 use super::*;
595
596 pub fn convert_precision<S, T>(value: Complex<S>) -> ConversionResult<Complex<T>>
598 where
599 S: Float + NumCast,
600 T: Float + NumCast,
601 {
602 let real = NumCast::from(value.re)
603 .ok_or_else(|| ConversionError::Failed("Cannot convert real part".to_string()))?;
604 let imag = NumCast::from(value.im)
605 .ok_or_else(|| ConversionError::Failed("Cannot convert imaginary part".to_string()))?;
606 Ok(Complex::new(real, imag))
607 }
608
609 pub fn convert_slice<S, T>(values: &[Complex<S>]) -> ConversionResult<Vec<Complex<T>>>
611 where
612 S: Float + NumCast,
613 T: Float + NumCast,
614 {
615 values.iter().map(|&v| convert_precision(v)).collect()
616 }
617
618 pub fn real_to_complex<T: Float + Zero>(values: &[T]) -> Vec<Complex<T>> {
620 values.iter().map(|&v| Complex::new(v, T::zero())).collect()
621 }
622
623 pub fn extract_real<T: Float>(values: &[Complex<T>]) -> Vec<T> {
625 values.iter().map(|v| v.re).collect()
626 }
627
628 pub fn extract_imag<T: Float>(values: &[Complex<T>]) -> Vec<T> {
630 values.iter().map(|v| v.im).collect()
631 }
632
633 pub fn to_magnitude<T: Float>(values: &[Complex<T>]) -> Vec<T> {
635 values
636 .iter()
637 .map(|v| (v.re * v.re + v.im * v.im).sqrt())
638 .collect()
639 }
640
641 pub fn to_phase<T: Float>(values: &[Complex<T>]) -> Vec<T> {
643 values.iter().map(|v| v.im.atan2(v.re)).collect()
644 }
645}
646
647pub mod batch {
649 use super::*;
650
651 #[derive(Debug)]
653 pub struct BatchResult<T> {
654 pub values: Vec<T>,
656 pub failed_indices: Vec<usize>,
658 pub errors: Vec<ConversionError>,
660 }
661
662 impl<T> BatchResult<T> {
663 #[must_use]
665 pub fn all_succeeded(&self) -> bool {
666 self.failed_indices.is_empty()
667 }
668
669 #[must_use]
671 pub fn success_count(&self) -> usize {
672 self.values.len() - self.failed_indices.len()
673 }
674
675 #[must_use]
677 pub fn failure_count(&self) -> usize {
678 self.failed_indices.len()
679 }
680 }
681
682 pub fn convert_tolerant<S, T>(values: &[S]) -> BatchResult<T>
684 where
685 S: NumCast + Copy,
686 T: NumCast + Copy + Default,
687 {
688 let mut result = BatchResult {
689 values: Vec::with_capacity(values.len()),
690 failed_indices: Vec::new(),
691 errors: Vec::new(),
692 };
693
694 for (i, &value) in values.iter().enumerate() {
695 match NumCast::from(value) {
696 Some(converted) => result.values.push(converted),
697 None => {
698 result.failed_indices.push(i);
699 result.errors.push(ConversionError::Failed(format!(
700 "Cannot convert value at index {i}"
701 )));
702 result.values.push(T::default());
703 }
704 }
705 }
706
707 result
708 }
709
710 pub fn convert_with_default<S, T>(values: &[S], default: T) -> Vec<T>
712 where
713 S: NumCast + Copy,
714 T: NumCast + Copy,
715 {
716 values
717 .iter()
718 .map(|&v| NumCast::from(v).unwrap_or(default))
719 .collect()
720 }
721
722 #[cfg(feature = "parallel")]
724 pub fn convert_parallel<S, T>(values: &[S], chunk_size: usize) -> ConversionResult<Vec<T>>
725 where
726 S: NumCast + Copy + Send + Sync,
727 T: NumCast + Copy + Send + Sync,
728 {
729 use rayon::prelude::*;
730
731 let results: Vec<ConversionResult<T>> = values
732 .par_chunks(chunk_size)
733 .flat_map(|chunk| {
734 chunk
735 .iter()
736 .map(|&v| {
737 NumCast::from(v)
738 .ok_or_else(|| ConversionError::Failed("Conversion failed".to_string()))
739 })
740 .collect::<Vec<_>>()
741 })
742 .collect();
743
744 results.into_iter().collect()
745 }
746}
747
748#[cfg(test)]
749mod tests {
750 use super::*;
751
752 #[test]
753 fn test_lossless_convert() {
754 let val: f64 = 2.5f32.convert_lossless().expect("Should convert");
755 assert!((val - 2.5f64).abs() < 0.001);
756
757 let val: i64 = 42i32.convert_lossless().expect("Should convert");
758 assert_eq!(val, 42);
759 }
760
761 #[test]
762 fn test_lossy_convert() {
763 let val: f32 = std::f64::consts::PI.convert_lossy();
764 assert!((val - std::f32::consts::PI).abs() < 1e-6);
765
766 let val: i32 = 3.7f64.convert_lossy();
767 assert_eq!(val, 3);
768 }
769
770 #[test]
771 fn test_lossy_convert_with_options() {
772 let options = ConversionOptions::strict();
773 let result: ConversionResult<i32> = 3.5f64.convert_with_options(&options);
774 assert!(result.is_err()); let options = ConversionOptions::lenient();
777 let result: ConversionResult<i32> = 3.5f64.convert_with_options(&options);
778 assert!(result.is_ok());
779 assert_eq!(result.expect("Should convert"), 4); }
781
782 #[test]
783 fn test_type_adapter() {
784 let adapter: TypeAdapter<f64, i32> = TypeAdapter::new();
785 let result = adapter.adapt(42.5);
786 assert!(result.is_ok());
787 assert_eq!(result.expect("Should adapt"), 42);
788
789 let values = [1.0, 2.0, 3.0, 4.0];
790 let adapted: Vec<i32> = adapter.adapt_slice(&values).expect("Should adapt slice");
791 assert_eq!(adapted, vec![1, 2, 3, 4]);
792 }
793
794 #[test]
795 fn test_data_flow_converter() {
796 let converter = DataFlowConverter::new();
797 let values = [1.5, 2.5, 3.5, 4.5];
798
799 let result: Vec<i32> = converter.convert(&values).expect("Should convert");
800 assert_eq!(result, vec![1, 2, 3, 4]);
801 }
802
803 #[test]
804 fn test_array_convert() {
805 let vec: Vec<f64> = vec![1.0, 2.0, 3.0];
806 let converted: Vec<i32> = vec.convert_array().expect("Should convert");
807 assert_eq!(converted, vec![1, 2, 3]);
808 }
809
810 #[test]
811 fn test_complex_conversion() {
812 let c64 = Complex::new(1.0f64, 2.0f64);
813 let c32: Complex<f32> = complex::convert_precision(c64).expect("Should convert");
814 assert!((c32.re - 1.0f32).abs() < 1e-6);
815 assert!((c32.im - 2.0f32).abs() < 1e-6);
816 }
817
818 #[test]
819 fn test_real_to_complex() {
820 let values = [1.0, 2.0, 3.0];
821 let complex_vals = complex::real_to_complex(&values);
822 assert_eq!(complex_vals.len(), 3);
823 assert_eq!(complex_vals[0].im, 0.0);
824 }
825
826 #[test]
827 fn test_batch_convert_tolerant() {
828 let values: Vec<f64> = vec![1.0, 2.0, f64::NAN, 4.0];
829 let result: batch::BatchResult<i32> = batch::convert_tolerant(&values);
830 assert!(result.success_count() + result.failure_count() == 4);
832 }
833
834 #[test]
835 fn test_conversion_error_display() {
836 let err = ConversionError::TypeMismatch {
837 expected: "f64".to_string(),
838 actual: "String".to_string(),
839 };
840 assert!(err.to_string().contains("f64"));
841
842 let err = ConversionError::OutOfRange {
843 value: "1e100".to_string(),
844 min: "-1e38".to_string(),
845 max: "1e38".to_string(),
846 };
847 assert!(err.to_string().contains("out of range"));
848 }
849
850 #[test]
851 fn test_conversion_options() {
852 let strict = ConversionOptions::strict();
853 assert!(!strict.allow_precision_loss);
854 assert!(!strict.clamp_to_range);
855
856 let lenient = ConversionOptions::lenient();
857 assert!(lenient.allow_precision_loss);
858 assert!(lenient.clamp_to_range);
859 }
860
861 #[test]
862 fn test_out_of_range_clamping() {
863 let options = ConversionOptions {
864 clamp_to_range: true,
865 ..Default::default()
866 };
867
868 let result: ConversionResult<i32> = 1e20f64.convert_with_options(&options);
869 assert!(result.is_ok());
870 assert_eq!(result.expect("Should clamp"), i32::MAX);
871
872 let result: ConversionResult<i32> = (-1e20f64).convert_with_options(&options);
873 assert!(result.is_ok());
874 assert_eq!(result.expect("Should clamp"), i32::MIN);
875 }
876}