use crate::config::Config;
use crate::version::{check_compatibility, VersionInfo};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Severity {
Info,
Warning,
Error,
}
#[derive(Debug, Clone)]
pub struct DiagnosticIssue {
pub severity: Severity,
pub component: String,
pub message: String,
pub suggestion: Option<String>,
}
impl DiagnosticIssue {
pub fn new(
severity: Severity,
component: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
severity,
component: component.into(),
message: message.into(),
suggestion: None,
}
}
#[must_use]
pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
self.suggestion = Some(suggestion.into());
self
}
}
impl fmt::Display for DiagnosticIssue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let severity_str = match self.severity {
Severity::Info => "INFO",
Severity::Warning => "WARN",
Severity::Error => "ERROR",
};
write!(f, "[{}] {}: {}", severity_str, self.component, self.message)?;
if let Some(ref suggestion) = self.suggestion {
write!(f, "\n Suggestion: {suggestion}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct FeatureInfo {
pub circuit: bool,
pub sim: bool,
pub ml: bool,
pub device: bool,
pub anneal: bool,
pub tytan: bool,
pub symengine: bool,
}
impl FeatureInfo {
pub const fn detect() -> Self {
Self {
circuit: cfg!(feature = "circuit"),
sim: cfg!(feature = "sim"),
ml: cfg!(feature = "ml"),
device: cfg!(feature = "device"),
anneal: cfg!(feature = "anneal"),
tytan: cfg!(feature = "tytan"),
symengine: cfg!(feature = "symengine"),
}
}
pub const fn count_enabled(&self) -> usize {
let mut count = 0;
if self.circuit {
count += 1;
}
if self.sim {
count += 1;
}
if self.ml {
count += 1;
}
if self.device {
count += 1;
}
if self.anneal {
count += 1;
}
if self.tytan {
count += 1;
}
if self.symengine {
count += 1;
}
count
}
}
#[derive(Debug, Clone)]
pub struct SystemCapabilities {
pub cpu_cores: usize,
pub total_memory_bytes: u64,
pub available_memory_bytes: u64,
pub has_gpu: bool,
pub has_avx2: bool,
pub has_avx512: bool,
pub has_neon: bool,
}
impl SystemCapabilities {
pub fn detect() -> Self {
let cpu_cores = num_cpus::get();
let (total_memory_bytes, available_memory_bytes) = Self::detect_memory();
let has_avx2 = Self::detect_avx2();
let has_avx512 = Self::detect_avx512();
let has_neon = cfg!(target_arch = "aarch64");
let has_gpu = Self::detect_gpu();
Self {
cpu_cores,
total_memory_bytes,
available_memory_bytes,
has_gpu,
has_avx2,
has_avx512,
has_neon,
}
}
fn detect_memory() -> (u64, u64) {
#[cfg(target_os = "macos")]
{
Self::detect_memory_macos()
}
#[cfg(target_os = "linux")]
{
Self::detect_memory_linux()
}
#[cfg(target_os = "windows")]
{
Self::detect_memory_windows()
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
{
(0, 0)
}
}
#[cfg(target_os = "macos")]
fn detect_memory_macos() -> (u64, u64) {
use std::process::Command;
let total = Command::new("sysctl")
.args(["-n", "hw.memsize"])
.output()
.ok()
.and_then(|output| {
if output.status.success() {
String::from_utf8(output.stdout)
.ok()
.and_then(|s| s.trim().parse::<u64>().ok())
} else {
None
}
})
.unwrap_or(0);
let available = Command::new("vm_stat")
.output()
.ok()
.and_then(|output| {
if output.status.success() {
let output_str = String::from_utf8(output.stdout).ok()?;
let mut free_pages = 0u64;
let mut inactive_pages = 0u64;
let page_size = 4096u64;
for line in output_str.lines() {
if line.contains("Pages free:") {
free_pages = line
.split(':')
.nth(1)
.and_then(|s| s.trim().trim_end_matches('.').parse().ok())
.unwrap_or(0);
} else if line.contains("Pages inactive:") {
inactive_pages = line
.split(':')
.nth(1)
.and_then(|s| s.trim().trim_end_matches('.').parse().ok())
.unwrap_or(0);
}
}
Some((free_pages + inactive_pages) * page_size)
} else {
None
}
})
.unwrap_or(0);
(total, available)
}
#[cfg(target_os = "linux")]
fn detect_memory_linux() -> (u64, u64) {
use std::fs;
let meminfo = fs::read_to_string("/proc/meminfo").unwrap_or_default();
let mut total = 0u64;
let mut available = 0u64;
for line in meminfo.lines() {
if line.starts_with("MemTotal:") {
total = line
.split_whitespace()
.nth(1)
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0)
* 1024; } else if line.starts_with("MemAvailable:") {
available = line
.split_whitespace()
.nth(1)
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(0)
* 1024; }
}
(total, available)
}
#[cfg(target_os = "windows")]
fn detect_memory_windows() -> (u64, u64) {
(0, 0)
}
#[allow(clippy::missing_const_for_fn)] fn detect_avx2() -> bool {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
if cfg!(target_feature = "avx2") {
return true;
}
std::arch::is_x86_feature_detected!("avx2")
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
false
}
}
#[allow(clippy::missing_const_for_fn)] fn detect_avx512() -> bool {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
{
if cfg!(target_feature = "avx512f") {
return true;
}
std::arch::is_x86_feature_detected!("avx512f")
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
{
false
}
}
#[allow(clippy::missing_const_for_fn)] #[allow(clippy::needless_return)] fn detect_gpu() -> bool {
#[cfg(target_os = "macos")]
{
true
}
#[cfg(any(target_os = "linux", target_os = "windows"))]
{
use std::process::Command;
Command::new("nvidia-smi")
.arg("--query-gpu=name")
.arg("--format=csv,noheader")
.output()
.map(|output| output.status.success())
.unwrap_or(false)
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
{
false
}
}
}
#[derive(Debug, Clone)]
pub struct DiagnosticReport {
pub version: VersionInfo,
pub capabilities: SystemCapabilities,
pub features: FeatureInfo,
pub config: crate::config::ConfigData,
pub issues: Vec<DiagnosticIssue>,
}
impl DiagnosticReport {
pub fn is_ready(&self) -> bool {
!self.has_errors()
}
pub fn has_errors(&self) -> bool {
self.issues
.iter()
.any(|issue| issue.severity == Severity::Error)
}
pub fn has_warnings(&self) -> bool {
self.issues
.iter()
.any(|issue| issue.severity == Severity::Warning)
}
pub fn errors(&self) -> Vec<&DiagnosticIssue> {
self.issues
.iter()
.filter(|issue| issue.severity == Severity::Error)
.collect()
}
pub fn warnings(&self) -> Vec<&DiagnosticIssue> {
self.issues
.iter()
.filter(|issue| issue.severity == Severity::Warning)
.collect()
}
pub fn summary(&self) -> String {
let errors = self.errors().len();
let warnings = self.warnings().len();
let info = self
.issues
.iter()
.filter(|i| i.severity == Severity::Info)
.count();
format!("Diagnostic Summary: {errors} errors, {warnings} warnings, {info} info messages")
}
}
impl fmt::Display for DiagnosticReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "=== QuantRS2 Diagnostic Report ===")?;
writeln!(f)?;
writeln!(f, "Version: {}", self.version.version_string())?;
writeln!(f, "Platform: {}", self.version.target)?;
writeln!(f, "Build Profile: {}", self.version.profile)?;
writeln!(f)?;
writeln!(f, "Enabled Features ({}):", self.features.count_enabled())?;
writeln!(f, " circuit: {}", self.features.circuit)?;
writeln!(f, " sim: {}", self.features.sim)?;
writeln!(f, " ml: {}", self.features.ml)?;
writeln!(f, " device: {}", self.features.device)?;
writeln!(f, " anneal: {}", self.features.anneal)?;
writeln!(f, " tytan: {}", self.features.tytan)?;
writeln!(f, " symengine: {}", self.features.symengine)?;
writeln!(f)?;
writeln!(f, "System Capabilities:")?;
writeln!(f, " CPU Cores: {}", self.capabilities.cpu_cores)?;
writeln!(
f,
" Total Memory: {:.2} GB",
self.capabilities.total_memory_bytes as f64 / (1024.0 * 1024.0 * 1024.0)
)?;
writeln!(
f,
" GPU Available: {}",
if self.capabilities.has_gpu {
"Yes"
} else {
"No"
}
)?;
writeln!(f, " AVX2: {}", self.capabilities.has_avx2)?;
writeln!(f, " AVX-512: {}", self.capabilities.has_avx512)?;
writeln!(f, " ARM NEON: {}", self.capabilities.has_neon)?;
writeln!(f)?;
writeln!(f, "Configuration:")?;
writeln!(
f,
" Threads: {}",
self.config
.num_threads
.map_or_else(|| "auto".to_string(), |n| n.to_string())
)?;
writeln!(f, " Log Level: {}", self.config.log_level.as_str())?;
writeln!(
f,
" Default Backend: {}",
self.config.default_backend.as_str()
)?;
writeln!(f, " GPU Enabled: {}", self.config.enable_gpu)?;
writeln!(f, " SIMD Enabled: {}", self.config.enable_simd)?;
writeln!(f)?;
if !self.issues.is_empty() {
writeln!(f, "Issues Found:")?;
for issue in &self.issues {
writeln!(f, " {issue}")?;
}
writeln!(f)?;
}
writeln!(f, "{}", self.summary())?;
if self.is_ready() {
writeln!(f)?;
writeln!(f, "System is READY for quantum simulation!")?;
} else if self.has_errors() {
writeln!(f)?;
writeln!(
f,
"System has CRITICAL ERRORS - please fix before proceeding"
)?;
} else if self.has_warnings() {
writeln!(f)?;
writeln!(
f,
"System is ready but has WARNINGS - review for optimal performance"
)?;
}
Ok(())
}
}
pub fn run_diagnostics() -> DiagnosticReport {
let version = VersionInfo::current();
let capabilities = SystemCapabilities::detect();
let features = FeatureInfo::detect();
let config = Config::global().snapshot();
let mut issues = Vec::new();
if let Err(compat_issues) = check_compatibility() {
for issue in compat_issues {
issues.push(
DiagnosticIssue::new(Severity::Error, "Compatibility", format!("{issue}"))
.with_suggestion("Update Rust version or fix feature flags"),
);
}
}
if capabilities.cpu_cores < 2 {
issues.push(
DiagnosticIssue::new(
Severity::Warning,
"CPU",
format!("Only {} CPU core(s) available", capabilities.cpu_cores),
)
.with_suggestion("Multi-core CPU recommended for better performance"),
);
}
if !capabilities.has_avx2 && !capabilities.has_neon {
issues.push(
DiagnosticIssue::new(
Severity::Warning,
"SIMD",
"No advanced SIMD support detected (AVX2/NEON)",
)
.with_suggestion("Performance may be limited without SIMD acceleration"),
);
}
if config.enable_gpu && !capabilities.has_gpu {
issues.push(
DiagnosticIssue::new(
Severity::Warning,
"GPU",
"GPU acceleration enabled but no GPU detected",
)
.with_suggestion("Disable GPU or install GPU drivers"),
);
}
if let Some(limit) = config.memory_limit_bytes {
if limit < 1024 * 1024 * 1024 {
issues.push(
DiagnosticIssue::new(
Severity::Warning,
"Memory",
format!("Memory limit is very low ({} MB)", limit / 1024 / 1024),
)
.with_suggestion("Increase memory limit for larger quantum systems"),
);
}
}
issues.push(DiagnosticIssue::new(
Severity::Info,
"QuantRS2",
format!("Running version {}", version.quantrs2),
));
issues.push(DiagnosticIssue::new(
Severity::Info,
"SciRS2",
format!("Using SciRS2 v{}", version.scirs2),
));
DiagnosticReport {
version,
capabilities,
features,
config,
issues,
}
}
pub fn is_ready() -> bool {
let report = run_diagnostics();
report.is_ready()
}
pub fn print_issues() {
let report = run_diagnostics();
for issue in &report.issues {
eprintln!("{issue}");
}
}
pub fn print_report() {
let report = run_diagnostics();
println!("{report}");
}
pub fn validate_or_panic() {
let report = run_diagnostics();
if !report.is_ready() {
eprintln!("{report}");
panic!("System validation failed - cannot continue");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_diagnostic_issue() {
let issue = DiagnosticIssue::new(Severity::Warning, "Test", "Test message")
.with_suggestion("Fix it");
assert_eq!(issue.severity, Severity::Warning);
assert_eq!(issue.component, "Test");
assert_eq!(issue.message, "Test message");
assert_eq!(issue.suggestion, Some("Fix it".to_string()));
}
#[test]
fn test_severity_ordering() {
assert!(Severity::Info < Severity::Warning);
assert!(Severity::Warning < Severity::Error);
}
#[test]
fn test_system_capabilities() {
let caps = SystemCapabilities::detect();
assert!(caps.cpu_cores > 0);
}
#[test]
fn test_run_diagnostics() {
let report = run_diagnostics();
assert!(!report.version.quantrs2.is_empty());
assert!(report.capabilities.cpu_cores > 0);
}
#[test]
fn test_is_ready() {
let ready = is_ready();
println!("System ready: {ready}");
}
#[test]
fn test_diagnostic_report_summary() {
let report = run_diagnostics();
let summary = report.summary();
assert!(summary.contains("Diagnostic Summary"));
}
}