1#![allow(dead_code)]
8use crate::{BackendResult, BackendType};
9use std::collections::HashMap;
10use torsh_core::error::TorshError;
11
12#[cfg(not(feature = "std"))]
13use alloc::{format, string::String, vec::Vec};
14
15#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
17pub struct Version {
18 pub major: u32,
20 pub minor: u32,
22 pub patch: u32,
24 pub pre_release: Option<String>,
26 pub build: Option<String>,
28}
29
30impl Version {
31 pub fn new(major: u32, minor: u32, patch: u32) -> Self {
33 Self {
34 major,
35 minor,
36 patch,
37 pre_release: None,
38 build: None,
39 }
40 }
41
42 pub fn with_pre_release(major: u32, minor: u32, patch: u32, pre_release: String) -> Self {
44 Self {
45 major,
46 minor,
47 patch,
48 pre_release: Some(pre_release),
49 build: None,
50 }
51 }
52
53 pub fn parse(version_str: &str) -> Result<Version, VersionError> {
55 let mut parts = version_str.split('+');
56 let version_part = parts.next().ok_or(VersionError::InvalidFormat)?;
57 let build = parts.next().map(|s| s.to_string());
58
59 let mut version_pre = version_part.split('-');
60 let version_core = version_pre.next().ok_or(VersionError::InvalidFormat)?;
61 let pre_release = version_pre.next().map(|s| s.to_string());
62
63 let core_parts: Vec<&str> = version_core.split('.').collect();
64 if core_parts.len() != 3 {
65 return Err(VersionError::InvalidFormat);
66 }
67
68 let major = core_parts[0]
69 .parse()
70 .map_err(|_| VersionError::InvalidNumber)?;
71 let minor = core_parts[1]
72 .parse()
73 .map_err(|_| VersionError::InvalidNumber)?;
74 let patch = core_parts[2]
75 .parse()
76 .map_err(|_| VersionError::InvalidNumber)?;
77
78 Ok(Version {
79 major,
80 minor,
81 patch,
82 pre_release,
83 build,
84 })
85 }
86
87 pub fn is_compatible_with(&self, other: &Version) -> bool {
89 self.major == other.major && self >= other
91 }
92
93 pub fn is_breaking_change_from(&self, other: &Version) -> bool {
95 self.major != other.major
96 }
97
98 pub fn to_string(&self) -> String {
100 let mut version = format!("{}.{}.{}", self.major, self.minor, self.patch);
101
102 if let Some(ref pre) = self.pre_release {
103 version.push('-');
104 version.push_str(pre);
105 }
106
107 if let Some(ref build) = self.build {
108 version.push('+');
109 version.push_str(build);
110 }
111
112 version
113 }
114}
115
116#[derive(Debug, Clone, PartialEq, Eq)]
118pub enum VersionError {
119 InvalidFormat,
121 InvalidNumber,
123 IncompatibleVersions(String, String),
125 MissingVersion(String),
127}
128
129impl std::fmt::Display for VersionError {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 match self {
132 VersionError::InvalidFormat => write!(f, "Invalid version format"),
133 VersionError::InvalidNumber => write!(f, "Invalid number in version string"),
134 VersionError::IncompatibleVersions(v1, v2) => {
135 write!(f, "Incompatible versions: {} and {}", v1, v2)
136 }
137 VersionError::MissingVersion(component) => {
138 write!(f, "Missing version for component: {}", component)
139 }
140 }
141 }
142}
143
144impl std::error::Error for VersionError {}
145
146#[derive(Debug, Clone)]
148pub struct BackendDependency {
149 pub name: String,
151 pub required_version: VersionRange,
153 pub current_version: Option<Version>,
155 pub optional: bool,
157 pub required_features: Vec<String>,
159}
160
161#[derive(Debug, Clone)]
163pub enum VersionRange {
164 Exact(Version),
166 Minimum(Version),
168 Range(Version, Version),
170 Compatible(Version),
172 Any,
174}
175
176impl VersionRange {
177 pub fn satisfies(&self, version: &Version) -> bool {
179 match self {
180 VersionRange::Exact(required) => version == required,
181 VersionRange::Minimum(min) => version >= min,
182 VersionRange::Range(min, max) => version >= min && version <= max,
183 VersionRange::Compatible(base) => version.is_compatible_with(base),
184 VersionRange::Any => true,
185 }
186 }
187
188 pub fn to_string(&self) -> String {
190 match self {
191 VersionRange::Exact(v) => format!("={}", v.to_string()),
192 VersionRange::Minimum(v) => format!(">={}", v.to_string()),
193 VersionRange::Range(min, max) => format!("{}-{}", min.to_string(), max.to_string()),
194 VersionRange::Compatible(v) => format!("~{}", v.to_string()),
195 VersionRange::Any => "*".to_string(),
196 }
197 }
198}
199
200#[derive(Debug, Clone)]
202pub struct ErrorContext {
203 pub operation: String,
205 pub backend_type: Option<BackendType>,
207 pub device_info: Option<String>,
209 pub location: Option<String>,
211 pub timestamp: Option<String>,
213 pub details: HashMap<String, String>,
215 pub suggestions: Vec<String>,
217 pub severity: ErrorSeverity,
219}
220
221#[derive(Debug, Clone, PartialEq, Eq)]
223pub enum ErrorSeverity {
224 Fatal,
226 Error,
228 Warning,
230 Info,
232}
233
234impl ErrorContext {
235 pub fn new(operation: &str) -> Self {
237 Self {
238 operation: operation.to_string(),
239 backend_type: None,
240 device_info: None,
241 location: None,
242 timestamp: None,
243 details: HashMap::new(),
244 suggestions: Vec::new(),
245 severity: ErrorSeverity::Error,
246 }
247 }
248
249 pub fn with_backend(mut self, backend_type: BackendType) -> Self {
251 self.backend_type = Some(backend_type);
252 self
253 }
254
255 pub fn with_device(mut self, device_info: &str) -> Self {
257 self.device_info = Some(device_info.to_string());
258 self
259 }
260
261 pub fn with_location(mut self, file: &str, line: u32) -> Self {
263 self.location = Some(format!("{}:{}", file, line));
264 self
265 }
266
267 pub fn with_detail(mut self, key: &str, value: &str) -> Self {
269 self.details.insert(key.to_string(), value.to_string());
270 self
271 }
272
273 pub fn with_suggestion(mut self, suggestion: &str) -> Self {
275 self.suggestions.push(suggestion.to_string());
276 self
277 }
278
279 pub fn with_severity(mut self, severity: ErrorSeverity) -> Self {
281 self.severity = severity;
282 self
283 }
284
285 pub fn format(&self) -> String {
287 let mut output = format!("Error in operation: {}", self.operation);
288
289 if let Some(ref backend) = self.backend_type {
290 output.push_str(&format!("\nBackend: {:?}", backend));
291 }
292
293 if let Some(ref device) = self.device_info {
294 output.push_str(&format!("\nDevice: {}", device));
295 }
296
297 if let Some(ref location) = self.location {
298 output.push_str(&format!("\nLocation: {}", location));
299 }
300
301 if !self.details.is_empty() {
302 output.push_str("\nDetails:");
303 for (key, value) in &self.details {
304 output.push_str(&format!("\n {}: {}", key, value));
305 }
306 }
307
308 if !self.suggestions.is_empty() {
309 output.push_str("\nSuggestions:");
310 for suggestion in &self.suggestions {
311 output.push_str(&format!("\n - {}", suggestion));
312 }
313 }
314
315 output
316 }
317}
318
319#[derive(Debug)]
321pub struct VersionCompatibilityChecker {
322 backend_versions: HashMap<BackendType, Version>,
324 dependencies: HashMap<BackendType, Vec<BackendDependency>>,
326 compatibility_matrix: HashMap<(BackendType, BackendType), bool>,
328}
329
330impl Default for VersionCompatibilityChecker {
331 fn default() -> Self {
332 Self::new()
333 }
334}
335
336impl VersionCompatibilityChecker {
337 pub fn new() -> Self {
339 let mut checker = Self {
340 backend_versions: HashMap::new(),
341 dependencies: HashMap::new(),
342 compatibility_matrix: HashMap::new(),
343 };
344
345 checker.initialize_known_versions();
347 checker.initialize_dependencies();
348 checker.build_compatibility_matrix();
349
350 checker
351 }
352
353 fn initialize_known_versions(&mut self) {
355 self.backend_versions
357 .insert(BackendType::Cpu, Version::new(0, 1, 0));
358
359 #[cfg(feature = "cuda")]
360 self.backend_versions
361 .insert(BackendType::Cuda, Version::new(0, 1, 0));
362
363 #[cfg(all(feature = "metal", target_os = "macos", target_arch = "aarch64"))]
364 self.backend_versions
365 .insert(BackendType::Metal, Version::new(0, 1, 0));
366
367 #[cfg(feature = "webgpu")]
368 self.backend_versions
369 .insert(BackendType::WebGpu, Version::new(0, 1, 0));
370 }
371
372 fn initialize_dependencies(&mut self) {
374 let cpu_deps = vec![
376 BackendDependency {
377 name: "scirs2-core".to_string(),
378 required_version: VersionRange::Compatible(Version::new(0, 1, 0)),
379 current_version: None,
380 optional: false,
381 required_features: vec!["cpu".to_string()],
382 },
383 BackendDependency {
384 name: "rayon".to_string(),
385 required_version: VersionRange::Minimum(Version::new(1, 5, 0)),
386 current_version: None,
387 optional: false,
388 required_features: vec![],
389 },
390 ];
391 self.dependencies.insert(BackendType::Cpu, cpu_deps);
392
393 #[cfg(feature = "cuda")]
395 {
396 let cuda_deps = vec![
397 BackendDependency {
398 name: "scirs2-core".to_string(),
399 required_version: VersionRange::Compatible(Version::new(0, 1, 0)),
400 current_version: None,
401 optional: false,
402 required_features: vec!["cuda".to_string()],
403 },
404 BackendDependency {
405 name: "cuda-runtime".to_string(),
406 required_version: VersionRange::Minimum(Version::new(11, 0, 0)),
407 current_version: None,
408 optional: false,
409 required_features: vec![],
410 },
411 ];
412 self.dependencies.insert(BackendType::Cuda, cuda_deps);
413 }
414 }
415
416 fn build_compatibility_matrix(&mut self) {
418 for backend_type in [
420 BackendType::Cpu,
421 BackendType::Cuda,
422 BackendType::Metal,
423 BackendType::WebGpu,
424 ] {
425 self.compatibility_matrix
426 .insert((backend_type, backend_type), true);
427 }
428
429 self.compatibility_matrix
431 .insert((BackendType::Cpu, BackendType::Cuda), true);
432 self.compatibility_matrix
433 .insert((BackendType::Cuda, BackendType::Cpu), true);
434 self.compatibility_matrix
435 .insert((BackendType::Cpu, BackendType::Metal), true);
436 self.compatibility_matrix
437 .insert((BackendType::Metal, BackendType::Cpu), true);
438 self.compatibility_matrix
440 .insert((BackendType::Cpu, BackendType::WebGpu), true);
441 self.compatibility_matrix
442 .insert((BackendType::WebGpu, BackendType::Cpu), true);
443 }
444
445 pub fn check_backend_compatibility(
447 &self,
448 backend_type: BackendType,
449 ) -> BackendResult<CompatibilityReport> {
450 let mut report = CompatibilityReport::new(backend_type);
451
452 if let Some(version) = self.backend_versions.get(&backend_type) {
454 report.backend_version = Some(version.clone());
455 } else {
456 report
457 .errors
458 .push(format!("Backend {:?} version not found", backend_type));
459 }
460
461 if let Some(deps) = self.dependencies.get(&backend_type) {
463 for dep in deps {
464 let dep_status = self.check_dependency(dep);
465 report
466 .dependency_status
467 .insert(dep.name.clone(), dep_status);
468 }
469 }
470
471 self.check_runtime_requirements(backend_type, &mut report);
473
474 Ok(report)
475 }
476
477 fn check_dependency(&self, dependency: &BackendDependency) -> DependencyStatus {
479 DependencyStatus {
482 available: true, current_version: dependency.current_version.clone(),
484 satisfies_requirement: true, missing_features: Vec::new(),
486 }
487 }
488
489 fn check_runtime_requirements(
491 &self,
492 backend_type: BackendType,
493 report: &mut CompatibilityReport,
494 ) {
495 match backend_type {
496 BackendType::Cuda => {
497 #[cfg(feature = "cuda")]
498 {
499 if !self.check_cuda_runtime() {
501 report.errors.push("CUDA runtime not available".to_string());
502 report
503 .suggestions
504 .push("Install CUDA toolkit and drivers".to_string());
505 }
506 }
507 #[cfg(not(feature = "cuda"))]
508 {
509 report
510 .errors
511 .push("CUDA backend not compiled in".to_string());
512 report
513 .suggestions
514 .push("Recompile with --features cuda".to_string());
515 }
516 }
517 BackendType::Metal => {
518 #[cfg(all(feature = "metal", target_os = "macos"))]
519 {
520 if !self.check_metal_availability() {
522 report
523 .errors
524 .push("Metal not available on this system".to_string());
525 }
526 }
527 #[cfg(not(all(feature = "metal", target_os = "macos")))]
528 {
529 report
530 .errors
531 .push("Metal backend only available on macOS".to_string());
532 }
533 }
534 BackendType::WebGpu => {
535 #[cfg(feature = "webgpu")]
536 {
537 if !self.check_webgpu_support() {
539 report
540 .warnings
541 .push("WebGPU support may be limited".to_string());
542 }
543 }
544 #[cfg(not(feature = "webgpu"))]
545 {
546 report
547 .errors
548 .push("WebGPU backend not compiled in".to_string());
549 }
550 }
551 BackendType::Cpu => {
552 self.check_cpu_features(report);
555 }
556 _ => {}
557 }
558 }
559
560 #[cfg(feature = "cuda")]
561 fn check_cuda_runtime(&self) -> bool {
562 true }
565
566 #[cfg(all(feature = "metal", target_os = "macos"))]
567 fn check_metal_availability(&self) -> bool {
568 true }
571
572 #[cfg(feature = "webgpu")]
573 fn check_webgpu_support(&self) -> bool {
574 true }
577
578 fn check_cpu_features(&self, _report: &mut CompatibilityReport) {
579 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
581 {
582 if !self.has_avx2() {
583 _report
584 .warnings
585 .push("AVX2 not available - performance may be reduced".to_string());
586 _report
587 .suggestions
588 .push("Consider using a newer CPU with AVX2 support".to_string());
589 }
590 }
591 }
592
593 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
594 fn has_avx2(&self) -> bool {
595 std::arch::is_x86_feature_detected!("avx2")
596 }
597
598 pub fn check_cross_backend_compatibility(&self, from: BackendType, to: BackendType) -> bool {
600 self.compatibility_matrix
601 .get(&(from, to))
602 .copied()
603 .unwrap_or(false)
604 }
605
606 pub fn get_compatible_backends(&self, backend_type: BackendType) -> Vec<BackendType> {
608 let mut compatible = Vec::new();
609
610 for other_backend in [
611 BackendType::Cpu,
612 BackendType::Cuda,
613 BackendType::Metal,
614 BackendType::WebGpu,
615 ] {
616 if self.check_cross_backend_compatibility(backend_type, other_backend) {
617 compatible.push(other_backend);
618 }
619 }
620
621 compatible
622 }
623}
624
625#[derive(Debug, Clone)]
627pub struct CompatibilityReport {
628 pub backend_type: BackendType,
630 pub backend_version: Option<Version>,
632 pub dependency_status: HashMap<String, DependencyStatus>,
634 pub errors: Vec<String>,
636 pub warnings: Vec<String>,
638 pub suggestions: Vec<String>,
640 pub compatibility_score: f32,
642}
643
644impl CompatibilityReport {
645 pub fn new(backend_type: BackendType) -> Self {
647 Self {
648 backend_type,
649 backend_version: None,
650 dependency_status: HashMap::new(),
651 errors: Vec::new(),
652 warnings: Vec::new(),
653 suggestions: Vec::new(),
654 compatibility_score: 0.0,
655 }
656 }
657
658 pub fn is_compatible(&self) -> bool {
660 self.errors.is_empty()
661 }
662
663 pub fn calculate_score(&mut self) {
665 let total_checks = 1 + self.dependency_status.len(); let mut passed_checks = 0;
667
668 if self.backend_version.is_some() {
670 passed_checks += 1;
671 }
672
673 for status in self.dependency_status.values() {
675 if status.satisfies_requirement {
676 passed_checks += 1;
677 }
678 }
679
680 self.compatibility_score = passed_checks as f32 / total_checks as f32;
681
682 let warning_penalty = self.warnings.len() as f32 * 0.1;
684 self.compatibility_score = (self.compatibility_score - warning_penalty).max(0.0);
685 }
686
687 pub fn format(&self) -> String {
689 let mut output = format!("Compatibility Report for {:?} Backend\n", self.backend_type);
690 output.push_str(&format!(
691 "Score: {:.1}%\n\n",
692 self.compatibility_score * 100.0
693 ));
694
695 if let Some(ref version) = self.backend_version {
696 output.push_str(&format!("Backend Version: {}\n", version.to_string()));
697 }
698
699 if !self.dependency_status.is_empty() {
700 output.push_str("\nDependencies:\n");
701 for (name, status) in &self.dependency_status {
702 let status_str = if status.satisfies_requirement {
703 "✓"
704 } else {
705 "✗"
706 };
707 output.push_str(&format!(" {} {}\n", status_str, name));
708 }
709 }
710
711 if !self.errors.is_empty() {
712 output.push_str("\nErrors:\n");
713 for error in &self.errors {
714 output.push_str(&format!(" ✗ {}\n", error));
715 }
716 }
717
718 if !self.warnings.is_empty() {
719 output.push_str("\nWarnings:\n");
720 for warning in &self.warnings {
721 output.push_str(&format!(" ⚠ {}\n", warning));
722 }
723 }
724
725 if !self.suggestions.is_empty() {
726 output.push_str("\nSuggestions:\n");
727 for suggestion in &self.suggestions {
728 output.push_str(&format!(" 💡 {}\n", suggestion));
729 }
730 }
731
732 output
733 }
734}
735
736#[derive(Debug, Clone)]
738pub struct DependencyStatus {
739 pub available: bool,
741 pub current_version: Option<Version>,
743 pub satisfies_requirement: bool,
745 pub missing_features: Vec<String>,
747}
748
749pub trait VersionErrorContextExt<T> {
751 fn with_operation(self, operation: &str) -> Result<T, TorshError>;
753
754 fn with_backend_context(
756 self,
757 backend_type: BackendType,
758 operation: &str,
759 ) -> Result<T, TorshError>;
760
761 fn with_location_context(self, file: &str, line: u32) -> Result<T, TorshError>;
763}
764
765impl<T> VersionErrorContextExt<T> for Result<T, TorshError> {
766 fn with_operation(self, operation: &str) -> Result<T, TorshError> {
767 self.map_err(|e| {
768 let context = ErrorContext::new(operation);
769 TorshError::BackendError(format!("{}\n{}", e, context.format()))
770 })
771 }
772
773 fn with_backend_context(
774 self,
775 backend_type: BackendType,
776 operation: &str,
777 ) -> Result<T, TorshError> {
778 self.map_err(|e| {
779 let context = ErrorContext::new(operation).with_backend(backend_type);
780 TorshError::BackendError(format!("{}\n{}", e, context.format()))
781 })
782 }
783
784 fn with_location_context(self, file: &str, line: u32) -> Result<T, TorshError> {
785 self.map_err(|e| {
786 let context = ErrorContext::new("unknown").with_location(file, line);
787 TorshError::BackendError(format!("{}\n{}", e, context.format()))
788 })
789 }
790}
791
792#[macro_export]
794macro_rules! error_with_location {
795 ($result:expr) => {
796 $result.with_location_context(file!(), line!())
797 };
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803
804 #[test]
805 fn test_version_parsing() {
806 let version = Version::parse("1.2.3").unwrap();
807 assert_eq!(version.major, 1);
808 assert_eq!(version.minor, 2);
809 assert_eq!(version.patch, 3);
810 assert_eq!(version.pre_release, None);
811
812 let version = Version::parse("2.0.0-alpha.1+build.123").unwrap();
813 assert_eq!(version.major, 2);
814 assert_eq!(version.pre_release, Some("alpha.1".to_string()));
815 assert_eq!(version.build, Some("build.123".to_string()));
816 }
817
818 #[test]
819 fn test_version_compatibility() {
820 let v1 = Version::new(1, 2, 3);
821 let v2 = Version::new(1, 2, 4);
822 let v3 = Version::new(2, 0, 0);
823
824 assert!(v2.is_compatible_with(&v1));
825 assert!(!v1.is_compatible_with(&v2));
826 assert!(v3.is_breaking_change_from(&v1));
827 }
828
829 #[test]
830 fn test_version_range() {
831 let version = Version::new(1, 2, 3);
832
833 let exact = VersionRange::Exact(Version::new(1, 2, 3));
834 assert!(exact.satisfies(&version));
835
836 let min = VersionRange::Minimum(Version::new(1, 0, 0));
837 assert!(min.satisfies(&version));
838
839 let compat = VersionRange::Compatible(Version::new(1, 1, 0));
840 assert!(compat.satisfies(&version));
841 }
842
843 #[test]
844 fn test_error_context() {
845 let context = ErrorContext::new("test_operation")
846 .with_backend(BackendType::Cpu)
847 .with_device("cpu:0")
848 .with_detail("tensor_size", "1000")
849 .with_suggestion("Reduce batch size");
850
851 let formatted = context.format();
852 assert!(formatted.contains("test_operation"));
853 assert!(formatted.contains("Backend: Cpu"));
854 assert!(formatted.contains("Device: cpu:0"));
855 }
856
857 #[test]
858 fn test_compatibility_checker() {
859 let checker = VersionCompatibilityChecker::new();
860
861 let report = checker
862 .check_backend_compatibility(BackendType::Cpu)
863 .unwrap();
864 assert_eq!(report.backend_type, BackendType::Cpu);
865
866 let compatible = checker.get_compatible_backends(BackendType::Cpu);
867 assert!(!compatible.is_empty());
868 }
869
870 #[test]
871 fn test_compatibility_report() {
872 let mut report = CompatibilityReport::new(BackendType::Cpu);
873 report.backend_version = Some(Version::new(0, 1, 0));
874 report.calculate_score();
875
876 assert!(report.compatibility_score > 0.0);
877 assert!(report.is_compatible());
878 }
879
880 #[test]
881 fn test_cross_backend_compatibility() {
882 let checker = VersionCompatibilityChecker::new();
883
884 assert!(checker.check_cross_backend_compatibility(BackendType::Cpu, BackendType::Cuda));
886 assert!(checker.check_cross_backend_compatibility(BackendType::Cuda, BackendType::Cpu));
887 }
888}