Skip to main content

quantrs2_sim/
auto_optimizer.rs

1//! `AutoOptimizer` for Automatic Backend Selection based on Problem Characteristics
2//!
3//! This module provides intelligent backend selection for quantum circuit simulation
4//! by analyzing circuit characteristics and automatically choosing the optimal
5//! execution backend using `SciRS2` optimization and analysis tools.
6
7use crate::{
8    automatic_parallelization::{AutoParallelConfig, AutoParallelEngine},
9    circuit_optimization::{CircuitOptimizer, OptimizationConfig},
10    distributed_simulator::{DistributedQuantumSimulator, DistributedSimulatorConfig},
11    error::{Result, SimulatorError},
12    large_scale_simulator::{LargeScaleQuantumSimulator, LargeScaleSimulatorConfig},
13    simulator::SimulatorResult,
14    statevector::StateVectorSimulator,
15};
16use quantrs2_circuit::builder::{Circuit, Simulator};
17use quantrs2_core::{
18    error::{QuantRS2Error, QuantRS2Result},
19    gate::GateOp,
20    qubit::QubitId,
21    register::Register,
22};
23use std::fmt::Write;
24
25#[cfg(all(feature = "gpu", not(target_os = "macos")))]
26use crate::gpu::SciRS2GpuStateVectorSimulator;
27use scirs2_core::parallel_ops::current_num_threads; // SciRS2 POLICY compliant
28use scirs2_core::Complex64;
29use serde::{Deserialize, Serialize};
30use std::collections::HashMap;
31use std::sync::Arc;
32use std::time::{Duration, Instant};
33
34/// Configuration for the `AutoOptimizer`
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct AutoOptimizerConfig {
37    /// Enable performance profiling during backend selection
38    pub enable_profiling: bool,
39    /// Memory budget for simulation (bytes)
40    pub memory_budget: usize,
41    /// CPU utilization threshold (0.0 to 1.0)
42    pub cpu_utilization_threshold: f64,
43    /// GPU availability check timeout
44    pub gpu_check_timeout: Duration,
45    /// Enable distributed simulation for large circuits
46    pub enable_distributed: bool,
47    /// `SciRS2` optimization level
48    pub scirs2_optimization_level: OptimizationLevel,
49    /// Fallback strategy when optimal backend is unavailable
50    pub fallback_strategy: FallbackStrategy,
51    /// Circuit complexity analysis depth
52    pub analysis_depth: AnalysisDepth,
53    /// Performance history cache size
54    pub performance_cache_size: usize,
55    /// Backend preference order
56    pub backend_preferences: Vec<BackendType>,
57}
58
59impl Default for AutoOptimizerConfig {
60    fn default() -> Self {
61        Self {
62            enable_profiling: true,
63            memory_budget: 8 * 1024 * 1024 * 1024, // 8GB
64            cpu_utilization_threshold: 0.8,
65            gpu_check_timeout: Duration::from_millis(1000),
66            enable_distributed: true,
67            scirs2_optimization_level: OptimizationLevel::Aggressive,
68            fallback_strategy: FallbackStrategy::Conservative,
69            analysis_depth: AnalysisDepth::Deep,
70            performance_cache_size: 1000,
71            backend_preferences: vec![
72                BackendType::SciRS2Gpu,
73                BackendType::LargeScale,
74                BackendType::Distributed,
75                BackendType::StateVector,
76            ],
77        }
78    }
79}
80
81/// Available backend types for optimization
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
83pub enum BackendType {
84    /// CPU state vector simulator
85    StateVector,
86    /// SciRS2-powered GPU simulator
87    SciRS2Gpu,
88    /// Large-scale optimized simulator
89    LargeScale,
90    /// Distributed cluster simulator
91    Distributed,
92    /// Automatic selection based on characteristics
93    Auto,
94}
95
96/// Optimization levels for `SciRS2` integration
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
98pub enum OptimizationLevel {
99    /// No optimization
100    None,
101    /// Basic optimizations
102    Basic,
103    /// Advanced optimizations
104    Advanced,
105    /// Aggressive optimizations with maximum `SciRS2` features
106    Aggressive,
107}
108
109/// Fallback strategies when optimal backend is unavailable
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111pub enum FallbackStrategy {
112    /// Conservative fallback to reliable backends
113    Conservative,
114    /// Aggressive fallback trying more experimental backends
115    Aggressive,
116    /// Fail if optimal backend is unavailable
117    Fail,
118}
119
120/// Circuit analysis depth levels
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
122pub enum AnalysisDepth {
123    /// Quick analysis with basic metrics
124    Quick,
125    /// Standard analysis with comprehensive metrics
126    Standard,
127    /// Deep analysis with advanced circuit characterization
128    Deep,
129}
130
131/// Circuit characteristics analysis results
132#[derive(Debug, Clone)]
133pub struct CircuitCharacteristics {
134    /// Number of qubits
135    pub num_qubits: usize,
136    /// Number of gates
137    pub num_gates: usize,
138    /// Circuit depth (longest path)
139    pub circuit_depth: usize,
140    /// Gate type distribution
141    pub gate_distribution: HashMap<String, usize>,
142    /// Parallelism potential (0.0 to 1.0)
143    pub parallelism_potential: f64,
144    /// Memory requirement estimate (bytes)
145    pub memory_requirement: usize,
146    /// Computational complexity score
147    pub complexity_score: f64,
148    /// Two-qubit gate density
149    pub two_qubit_density: f64,
150    /// Connectivity graph properties
151    pub connectivity_properties: ConnectivityProperties,
152    /// Entanglement depth estimate
153    pub entanglement_depth: usize,
154    /// Noise susceptibility score
155    pub noise_susceptibility: f64,
156}
157
158/// Connectivity graph properties of the circuit
159#[derive(Debug, Clone)]
160pub struct ConnectivityProperties {
161    /// Maximum degree of connectivity
162    pub max_degree: usize,
163    /// Average degree of connectivity
164    pub avg_degree: f64,
165    /// Number of connected components
166    pub connected_components: usize,
167    /// Circuit diameter (longest path between any two qubits)
168    pub diameter: usize,
169    /// Clustering coefficient
170    pub clustering_coefficient: f64,
171}
172
173/// Backend recommendation with reasoning
174#[derive(Debug, Clone)]
175pub struct BackendRecommendation {
176    /// Recommended backend type
177    pub backend_type: BackendType,
178    /// Confidence score (0.0 to 1.0)
179    pub confidence: f64,
180    /// Expected performance improvement over baseline
181    pub expected_improvement: f64,
182    /// Estimated execution time
183    pub estimated_execution_time: Duration,
184    /// Estimated memory usage
185    pub estimated_memory_usage: usize,
186    /// Reasoning for the recommendation
187    pub reasoning: String,
188    /// Alternative recommendations
189    pub alternatives: Vec<(BackendType, f64)>,
190    /// Performance prediction model used
191    pub prediction_model: String,
192}
193
194/// Performance metrics for backend selection
195#[derive(Debug, Clone)]
196pub struct PerformanceMetrics {
197    /// Execution time (measured wall-clock duration of the run)
198    pub execution_time: Duration,
199    /// Memory required for the dense state vector of this circuit, in bytes
200    /// (`2^num_qubits * size_of::<Complex64>()`). This is the dominant, exact
201    /// memory cost of state-vector simulation; it is computed, not measured by a
202    /// profiler.
203    pub memory_usage: usize,
204    /// CPU utilization during the run. In-process CPU utilization cannot be
205    /// measured without an external profiler / OS sampling, so this is `None`
206    /// unless a caller supplies a measured value.
207    pub cpu_utilization: Option<f64>,
208    /// GPU utilization (if applicable)
209    pub gpu_utilization: Option<f64>,
210    /// Throughput (gates per second), derived from `execution_time`
211    pub throughput: f64,
212    /// Error rate. `None` when no error model was applied (no error to report);
213    /// a measured/estimated rate when available.
214    pub error_rate: Option<f64>,
215}
216
217/// Performance history entry for caching
218#[derive(Debug, Clone)]
219pub struct PerformanceHistory {
220    /// Circuit characteristics hash
221    pub circuit_hash: u64,
222    /// Backend used
223    pub backend_type: BackendType,
224    /// Performance metrics achieved
225    pub metrics: PerformanceMetrics,
226    /// Timestamp
227    pub timestamp: Instant,
228}
229
230/// `AutoOptimizer` for intelligent backend selection
231pub struct AutoOptimizer {
232    /// Configuration
233    config: AutoOptimizerConfig,
234    /// Circuit optimizer for preprocessing
235    circuit_optimizer: CircuitOptimizer,
236    /// Parallelization engine
237    parallel_engine: AutoParallelEngine,
238    /// Performance history cache
239    performance_cache: Vec<PerformanceHistory>,
240    /// Backend availability cache
241    backend_availability: HashMap<BackendType, bool>,
242    /// `SciRS2` analysis tools integration
243    scirs2_analyzer: SciRS2CircuitAnalyzer,
244}
245
246/// SciRS2-powered circuit analyzer
247struct SciRS2CircuitAnalyzer {
248    /// Enable advanced `SciRS2` features
249    enable_advanced_features: bool,
250}
251
252impl AutoOptimizer {
253    /// Create a new `AutoOptimizer` with default configuration
254    #[must_use]
255    pub fn new() -> Self {
256        Self::with_config(AutoOptimizerConfig::default())
257    }
258
259    /// Create a new `AutoOptimizer` with custom configuration
260    #[must_use]
261    pub fn with_config(config: AutoOptimizerConfig) -> Self {
262        let optimization_config = OptimizationConfig {
263            enable_gate_fusion: true,
264            enable_redundant_elimination: true,
265            enable_commutation_reordering: true,
266            enable_single_qubit_optimization: true,
267            enable_two_qubit_optimization: true,
268            max_passes: 3,
269            enable_depth_reduction: true,
270        };
271
272        let parallel_config = AutoParallelConfig {
273            max_threads: current_num_threads(), // SciRS2 POLICY compliant
274            min_gates_for_parallel: 20,
275            strategy: crate::automatic_parallelization::ParallelizationStrategy::Hybrid,
276            ..Default::default()
277        };
278
279        Self {
280            config,
281            circuit_optimizer: CircuitOptimizer::with_config(optimization_config),
282            parallel_engine: AutoParallelEngine::new(parallel_config),
283            performance_cache: Vec::new(),
284            backend_availability: HashMap::new(),
285            scirs2_analyzer: SciRS2CircuitAnalyzer {
286                enable_advanced_features: true,
287            },
288        }
289    }
290
291    /// Analyze circuit characteristics using `SciRS2` tools
292    pub fn analyze_circuit<const N: usize>(
293        &self,
294        circuit: &Circuit<N>,
295    ) -> QuantRS2Result<CircuitCharacteristics> {
296        let start_time = Instant::now();
297
298        // Basic circuit metrics
299        let num_qubits = circuit.num_qubits();
300        let num_gates = circuit.num_gates();
301        let circuit_depth = self.calculate_circuit_depth(circuit);
302
303        // Gate distribution analysis
304        let gate_distribution = self.analyze_gate_distribution(circuit);
305
306        // Parallelism analysis using SciRS2
307        let parallelism_potential = self.analyze_parallelism_potential(circuit)?;
308
309        // Memory requirement estimation
310        let memory_requirement = self.estimate_memory_requirement(num_qubits, num_gates);
311
312        // Complexity scoring using SciRS2 complexity analysis
313        let complexity_score = self.calculate_complexity_score(circuit)?;
314
315        // Two-qubit gate analysis
316        let two_qubit_density = self.calculate_two_qubit_density(circuit);
317
318        // Connectivity analysis
319        let connectivity_properties = self.analyze_connectivity(circuit)?;
320
321        // Entanglement depth estimation using SciRS2
322        let entanglement_depth = self.estimate_entanglement_depth(circuit)?;
323
324        // Noise susceptibility analysis
325        let noise_susceptibility = self.analyze_noise_susceptibility(circuit);
326
327        let analysis_time = start_time.elapsed();
328        if self.config.enable_profiling {
329            println!("Circuit analysis completed in {analysis_time:?}");
330        }
331
332        Ok(CircuitCharacteristics {
333            num_qubits,
334            num_gates,
335            circuit_depth,
336            gate_distribution,
337            parallelism_potential,
338            memory_requirement,
339            complexity_score,
340            two_qubit_density,
341            connectivity_properties,
342            entanglement_depth,
343            noise_susceptibility,
344        })
345    }
346
347    /// Recommend optimal backend based on circuit characteristics
348    pub fn recommend_backend<const N: usize>(
349        &mut self,
350        circuit: &Circuit<N>,
351    ) -> QuantRS2Result<BackendRecommendation> {
352        // Analyze circuit characteristics
353        let characteristics = self.analyze_circuit(circuit)?;
354
355        // Check backend availability
356        self.update_backend_availability()?;
357
358        // Check performance cache for similar circuits
359        if let Some(cached_result) = self.check_performance_cache(&characteristics) {
360            return Ok(self.build_recommendation_from_cache(cached_result));
361        }
362
363        // Generate recommendations based on characteristics
364        let recommendation = self.generate_backend_recommendation(&characteristics)?;
365
366        Ok(recommendation)
367    }
368
369    /// Execute circuit with automatic backend selection
370    pub fn execute_optimized<const N: usize>(
371        &mut self,
372        circuit: &Circuit<N>,
373    ) -> Result<SimulatorResult<N>> {
374        // Get backend recommendation
375        let recommendation = self
376            .recommend_backend(circuit)
377            .map_err(|e| SimulatorError::ComputationError(e.to_string()))?;
378
379        if self.config.enable_profiling {
380            println!(
381                "Using {} backend (confidence: {:.2})",
382                self.backend_type_name(recommendation.backend_type),
383                recommendation.confidence
384            );
385            println!("Reasoning: {}", recommendation.reasoning);
386        }
387
388        // Execute with recommended backend
389        let start_time = Instant::now();
390        let register = self.execute_with_backend(circuit, recommendation.backend_type)?;
391        let execution_time = start_time.elapsed();
392
393        // Convert Register to SimulatorResult
394        let result = self.register_to_simulator_result(register);
395
396        // Record performance metrics
397        if self.config.enable_profiling {
398            self.record_performance_metrics(circuit, recommendation.backend_type, execution_time);
399            println!("Execution completed in {execution_time:?}");
400        }
401
402        Ok(result)
403    }
404
405    /// Calculate circuit depth (critical path length)
406    fn calculate_circuit_depth<const N: usize>(&self, circuit: &Circuit<N>) -> usize {
407        let mut qubit_depths = HashMap::new();
408        let mut max_depth = 0;
409
410        for gate in circuit.gates() {
411            let qubits = gate.qubits();
412
413            // Find maximum depth among input qubits
414            let input_depth = qubits
415                .iter()
416                .map(|&q| qubit_depths.get(&q).copied().unwrap_or(0))
417                .max()
418                .unwrap_or(0);
419
420            let new_depth = input_depth + 1;
421
422            // Update depths for all output qubits
423            for &qubit in &qubits {
424                qubit_depths.insert(qubit, new_depth);
425            }
426
427            max_depth = max_depth.max(new_depth);
428        }
429
430        max_depth
431    }
432
433    /// Analyze gate distribution in the circuit
434    fn analyze_gate_distribution<const N: usize>(
435        &self,
436        circuit: &Circuit<N>,
437    ) -> HashMap<String, usize> {
438        let mut distribution = HashMap::new();
439
440        for gate in circuit.gates() {
441            let gate_name = gate.name().to_string();
442            *distribution.entry(gate_name).or_insert(0) += 1;
443        }
444
445        distribution
446    }
447
448    /// Analyze parallelism potential using `SciRS2` parallel ops
449    fn analyze_parallelism_potential<const N: usize>(
450        &self,
451        circuit: &Circuit<N>,
452    ) -> QuantRS2Result<f64> {
453        // Use SciRS2-powered parallelization analysis
454        let analysis = self.parallel_engine.analyze_circuit(circuit)?;
455        Ok(analysis.efficiency)
456    }
457
458    /// Estimate memory requirement for circuit simulation
459    const fn estimate_memory_requirement(&self, num_qubits: usize, num_gates: usize) -> usize {
460        // State vector memory: 2^n complex numbers
461        let state_vector_size = (1 << num_qubits) * std::mem::size_of::<Complex64>();
462
463        // Additional overhead for gate operations and intermediate results
464        let overhead = num_gates * 64; // Rough estimate
465
466        state_vector_size + overhead
467    }
468
469    /// Calculate circuit complexity score using `SciRS2` complexity analysis
470    fn calculate_complexity_score<const N: usize>(
471        &self,
472        circuit: &Circuit<N>,
473    ) -> QuantRS2Result<f64> {
474        let num_qubits = circuit.num_qubits() as f64;
475        let num_gates = circuit.num_gates() as f64;
476        let depth = self.calculate_circuit_depth(circuit) as f64;
477
478        // SciRS2-inspired complexity scoring
479        let gate_complexity = num_gates * (num_qubits.log2() + 1.0);
480        let depth_complexity = depth * num_qubits;
481        let entanglement_complexity = self.estimate_entanglement_complexity(circuit)?;
482
483        // Real structural-richness factor in [0, 1] derived from the circuit:
484        // denser, more entangling, higher-arity circuits scale the base score up.
485        let structural_richness = self.scirs2_analyzer.analyze_circuit_with_scirs2(circuit)?;
486        let richness_factor = 1.0 + structural_richness;
487
488        Ok(
489            (gate_complexity + depth_complexity + entanglement_complexity) * richness_factor
490                / 1000.0,
491        )
492    }
493
494    /// Estimate entanglement complexity
495    fn estimate_entanglement_complexity<const N: usize>(
496        &self,
497        circuit: &Circuit<N>,
498    ) -> QuantRS2Result<f64> {
499        let mut entanglement_score = 0.0;
500
501        for gate in circuit.gates() {
502            let qubits = gate.qubits();
503            if qubits.len() >= 2 {
504                // Two-qubit gates increase entanglement complexity
505                entanglement_score += qubits.len() as f64 * qubits.len() as f64;
506            }
507        }
508
509        Ok(entanglement_score)
510    }
511
512    /// Calculate two-qubit gate density
513    fn calculate_two_qubit_density<const N: usize>(&self, circuit: &Circuit<N>) -> f64 {
514        let total_gates = circuit.num_gates();
515        if total_gates == 0 {
516            return 0.0;
517        }
518
519        let two_qubit_gates = circuit
520            .gates()
521            .iter()
522            .filter(|gate| gate.qubits().len() >= 2)
523            .count();
524
525        two_qubit_gates as f64 / total_gates as f64
526    }
527
528    /// Analyze circuit connectivity using `SciRS2` graph analysis
529    fn analyze_connectivity<const N: usize>(
530        &self,
531        circuit: &Circuit<N>,
532    ) -> QuantRS2Result<ConnectivityProperties> {
533        let mut qubit_connections: HashMap<QubitId, Vec<QubitId>> = HashMap::new();
534
535        // Build connectivity graph
536        for gate in circuit.gates() {
537            let qubits = gate.qubits();
538            if qubits.len() >= 2 {
539                for i in 0..qubits.len() {
540                    for j in (i + 1)..qubits.len() {
541                        qubit_connections
542                            .entry(qubits[i])
543                            .or_default()
544                            .push(qubits[j]);
545                        qubit_connections
546                            .entry(qubits[j])
547                            .or_default()
548                            .push(qubits[i]);
549                    }
550                }
551            }
552        }
553
554        // Deduplicate adjacency into undirected neighbor sets (2-qubit gates may
555        // appear multiple times between the same pair).
556        let adjacency: HashMap<QubitId, std::collections::HashSet<QubitId>> = qubit_connections
557            .iter()
558            .map(|(&node, neighbors)| {
559                let set: std::collections::HashSet<QubitId> =
560                    neighbors.iter().copied().filter(|&n| n != node).collect();
561                (node, set)
562            })
563            .collect();
564
565        // Calculate connectivity properties from the deduplicated graph
566        let max_degree = adjacency
567            .values()
568            .map(std::collections::HashSet::len)
569            .max()
570            .unwrap_or(0);
571
572        let avg_degree = if adjacency.is_empty() {
573            0.0
574        } else {
575            adjacency
576                .values()
577                .map(std::collections::HashSet::len)
578                .sum::<usize>() as f64
579                / adjacency.len() as f64
580        };
581
582        // Real connected-components count over the interaction graph (BFS).
583        // Isolated qubits (never touched by a 2-qubit gate) are each their own
584        // component, matching the total qubit count of the circuit.
585        let connected_components =
586            Self::count_connected_components(&adjacency, circuit.num_qubits());
587
588        // Real graph diameter: the longest shortest-path between any two qubits
589        // in the interaction graph (computed via BFS from each node). Disconnected
590        // graphs report the largest finite eccentricity found.
591        let diameter = Self::graph_diameter(&adjacency);
592
593        // Real global clustering coefficient: average of per-node local clustering
594        // coefficients, where each node's coefficient is the fraction of its
595        // neighbor-pairs that are themselves connected (closed triplets).
596        let clustering_coefficient = Self::clustering_coefficient(&adjacency);
597
598        Ok(ConnectivityProperties {
599            max_degree,
600            avg_degree,
601            connected_components,
602            diameter,
603            clustering_coefficient,
604        })
605    }
606
607    /// Count connected components of the qubit-interaction graph.
608    ///
609    /// Qubits that never participate in a 2-qubit gate are not present in the
610    /// adjacency map; each such qubit forms its own singleton component.
611    fn count_connected_components(
612        adjacency: &HashMap<QubitId, std::collections::HashSet<QubitId>>,
613        total_qubits: usize,
614    ) -> usize {
615        use std::collections::{HashSet, VecDeque};
616
617        let mut visited: HashSet<QubitId> = HashSet::new();
618        let mut components = 0;
619
620        for &start in adjacency.keys() {
621            if visited.contains(&start) {
622                continue;
623            }
624            components += 1;
625            let mut queue = VecDeque::new();
626            queue.push_back(start);
627            visited.insert(start);
628            while let Some(node) = queue.pop_front() {
629                if let Some(neighbors) = adjacency.get(&node) {
630                    for &next in neighbors {
631                        if visited.insert(next) {
632                            queue.push_back(next);
633                        }
634                    }
635                }
636            }
637        }
638
639        // Qubits absent from the interaction graph are isolated singletons.
640        let connected_qubits = visited.len();
641        let isolated = total_qubits.saturating_sub(connected_qubits);
642        components + isolated
643    }
644
645    /// Compute the diameter (longest shortest-path) of the interaction graph.
646    ///
647    /// Runs a BFS from every node and tracks the maximum finite distance found.
648    /// For a graph with no edges this is 0.
649    fn graph_diameter(adjacency: &HashMap<QubitId, std::collections::HashSet<QubitId>>) -> usize {
650        use std::collections::hash_map::Entry;
651        use std::collections::{HashMap as Map, VecDeque};
652
653        let mut diameter = 0;
654        for &source in adjacency.keys() {
655            let mut distances: Map<QubitId, usize> = Map::new();
656            distances.insert(source, 0);
657            let mut queue = VecDeque::new();
658            queue.push_back(source);
659            while let Some(node) = queue.pop_front() {
660                let current_dist = distances.get(&node).copied().unwrap_or(0);
661                if let Some(neighbors) = adjacency.get(&node) {
662                    for &next in neighbors {
663                        if let Entry::Vacant(slot) = distances.entry(next) {
664                            slot.insert(current_dist + 1);
665                            queue.push_back(next);
666                        }
667                    }
668                }
669            }
670            if let Some(&max_dist) = distances.values().max() {
671                diameter = diameter.max(max_dist);
672            }
673        }
674        diameter
675    }
676
677    /// Compute the global clustering coefficient (average local clustering).
678    ///
679    /// For each node with degree >= 2, the local coefficient is
680    /// `2 * links_between_neighbors / (degree * (degree - 1))`. The global value
681    /// is the mean over all nodes with degree >= 2. Returns 0.0 when no such node
682    /// exists (e.g. a graph with no triangles possible).
683    fn clustering_coefficient(
684        adjacency: &HashMap<QubitId, std::collections::HashSet<QubitId>>,
685    ) -> f64 {
686        let mut sum = 0.0;
687        let mut counted = 0usize;
688
689        for neighbors in adjacency.values() {
690            let degree = neighbors.len();
691            if degree < 2 {
692                continue;
693            }
694            let neighbor_list: Vec<QubitId> = neighbors.iter().copied().collect();
695            let mut links = 0usize;
696            for i in 0..neighbor_list.len() {
697                for j in (i + 1)..neighbor_list.len() {
698                    if let Some(set) = adjacency.get(&neighbor_list[i]) {
699                        if set.contains(&neighbor_list[j]) {
700                            links += 1;
701                        }
702                    }
703                }
704            }
705            let possible = degree * (degree - 1) / 2;
706            sum += links as f64 / possible as f64;
707            counted += 1;
708        }
709
710        if counted == 0 {
711            0.0
712        } else {
713            sum / counted as f64
714        }
715    }
716
717    /// Estimate entanglement depth using `SciRS2` analysis
718    fn estimate_entanglement_depth<const N: usize>(
719        &self,
720        circuit: &Circuit<N>,
721    ) -> QuantRS2Result<usize> {
722        // Simplified entanglement depth estimation
723        let two_qubit_gates = circuit
724            .gates()
725            .iter()
726            .filter(|gate| gate.qubits().len() >= 2)
727            .count();
728
729        // Rough estimate based on two-qubit gate count and circuit structure
730        let depth_estimate = (two_qubit_gates as f64).sqrt().ceil() as usize;
731        Ok(depth_estimate.min(circuit.num_qubits()))
732    }
733
734    /// Analyze noise susceptibility
735    fn analyze_noise_susceptibility<const N: usize>(&self, circuit: &Circuit<N>) -> f64 {
736        let depth = self.calculate_circuit_depth(circuit) as f64;
737        let two_qubit_density = self.calculate_two_qubit_density(circuit);
738
739        // Circuits with higher depth and more two-qubit gates are more susceptible to noise
740        (depth / 100.0 + two_qubit_density).min(1.0)
741    }
742
743    /// Update backend availability status
744    fn update_backend_availability(&mut self) -> QuantRS2Result<()> {
745        // Check GPU availability
746        #[cfg(all(feature = "gpu", not(target_os = "macos")))]
747        let gpu_available = SciRS2GpuStateVectorSimulator::is_available();
748        #[cfg(any(not(feature = "gpu"), target_os = "macos"))]
749        let gpu_available = false;
750
751        self.backend_availability
752            .insert(BackendType::SciRS2Gpu, gpu_available);
753
754        // CPU backends are always available
755        self.backend_availability
756            .insert(BackendType::StateVector, true);
757        self.backend_availability
758            .insert(BackendType::LargeScale, true);
759
760        // Distributed availability would require cluster check
761        self.backend_availability
762            .insert(BackendType::Distributed, false);
763
764        Ok(())
765    }
766
767    /// Check performance cache for similar circuits
768    fn check_performance_cache(
769        &self,
770        characteristics: &CircuitCharacteristics,
771    ) -> Option<&PerformanceHistory> {
772        // Simple cache lookup based on circuit characteristics
773        // In practice, would use more sophisticated similarity matching
774        self.performance_cache
775            .iter()
776            .find(|&entry| self.are_characteristics_similar(characteristics, entry))
777            .map(|v| v as _)
778    }
779
780    /// Check if circuit characteristics are similar to a cached entry.
781    ///
782    /// Similarity is determined by hashing the characteristics into the same
783    /// coarse bucket used for the cache key (qubit count, gate-count order of
784    /// magnitude, two-qubit-density band). A cache entry only matches when its
785    /// stored circuit hash falls in the same bucket, ensuring we never reuse a
786    /// recommendation for a structurally different circuit.
787    fn are_characteristics_similar(
788        &self,
789        characteristics: &CircuitCharacteristics,
790        entry: &PerformanceHistory,
791    ) -> bool {
792        self.characteristics_bucket_hash(characteristics) == entry.circuit_hash
793    }
794
795    /// Compute a coarse bucket hash for circuit characteristics.
796    ///
797    /// Circuits land in the same bucket when they share the same qubit count,
798    /// the same gate-count order of magnitude, and the same two-qubit-density
799    /// decile. This is the same key written when recording performance metrics,
800    /// so a hit means the cached backend was measured on a comparable workload.
801    fn characteristics_bucket_hash(&self, characteristics: &CircuitCharacteristics) -> u64 {
802        use std::collections::hash_map::DefaultHasher;
803        use std::hash::{Hash, Hasher};
804
805        let mut hasher = DefaultHasher::new();
806        characteristics.num_qubits.hash(&mut hasher);
807        // Bucket gate count by order of magnitude to tolerate small variation.
808        let gate_magnitude = (characteristics.num_gates as f64).max(1.0).log10().floor() as i64;
809        gate_magnitude.hash(&mut hasher);
810        // Bucket two-qubit density into deciles.
811        let density_decile = (characteristics.two_qubit_density * 10.0).round() as i64;
812        density_decile.hash(&mut hasher);
813        hasher.finish()
814    }
815
816    /// Build recommendation from cached performance data
817    fn build_recommendation_from_cache(
818        &self,
819        cache_entry: &PerformanceHistory,
820    ) -> BackendRecommendation {
821        BackendRecommendation {
822            backend_type: cache_entry.backend_type,
823            confidence: 0.9, // High confidence for cached results
824            expected_improvement: 0.0,
825            estimated_execution_time: cache_entry.metrics.execution_time,
826            estimated_memory_usage: cache_entry.metrics.memory_usage,
827            reasoning: "Based on cached performance data for similar circuits".to_string(),
828            alternatives: Vec::new(),
829            prediction_model: "Cache-based".to_string(),
830        }
831    }
832
833    /// Generate backend recommendation based on circuit characteristics
834    fn generate_backend_recommendation(
835        &self,
836        characteristics: &CircuitCharacteristics,
837    ) -> QuantRS2Result<BackendRecommendation> {
838        let mut scores: HashMap<BackendType, f64> = HashMap::new();
839        let mut reasoning = String::new();
840
841        // Score different backends based on circuit characteristics
842        for &backend_type in &self.config.backend_preferences {
843            if !self
844                .backend_availability
845                .get(&backend_type)
846                .unwrap_or(&false)
847            {
848                continue;
849            }
850
851            let score = self.score_backend_for_characteristics(backend_type, characteristics);
852            scores.insert(backend_type, score);
853        }
854
855        // Find the best backend
856        let (best_backend, best_score) = scores
857            .into_iter()
858            .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
859            .unwrap_or((BackendType::StateVector, 0.5));
860
861        // Generate reasoning
862        reasoning = self.generate_recommendation_reasoning(best_backend, characteristics);
863
864        // Estimate performance
865        let estimated_execution_time = self.estimate_execution_time(best_backend, characteristics);
866        let estimated_memory_usage = characteristics.memory_requirement;
867
868        Ok(BackendRecommendation {
869            backend_type: best_backend,
870            confidence: best_score,
871            expected_improvement: (best_score - 0.5).max(0.0) * 2.0, // Normalize to improvement
872            estimated_execution_time,
873            estimated_memory_usage,
874            reasoning,
875            alternatives: Vec::new(),
876            prediction_model: "SciRS2-guided heuristic".to_string(),
877        })
878    }
879
880    /// Score a backend for given circuit characteristics
881    fn score_backend_for_characteristics(
882        &self,
883        backend_type: BackendType,
884        characteristics: &CircuitCharacteristics,
885    ) -> f64 {
886        let mut score: f64 = 0.5; // Base score
887
888        match backend_type {
889            BackendType::StateVector => {
890                // Good for small circuits
891                if characteristics.num_qubits <= 20 {
892                    score += 0.3;
893                }
894                if characteristics.num_gates <= 1000 {
895                    score += 0.2;
896                }
897            }
898            BackendType::SciRS2Gpu => {
899                // Good for medium to large circuits with high parallelism
900                if characteristics.num_qubits >= 10 && characteristics.num_qubits <= 30 {
901                    score += 0.4;
902                }
903                if characteristics.parallelism_potential > 0.5 {
904                    score += 0.3;
905                }
906                if characteristics.two_qubit_density > 0.3 {
907                    score += 0.2;
908                }
909            }
910            BackendType::LargeScale => {
911                // Good for large circuits
912                if characteristics.num_qubits >= 20 {
913                    score += 0.4;
914                }
915                if characteristics.complexity_score > 0.5 {
916                    score += 0.3;
917                }
918            }
919            BackendType::Distributed => {
920                // Good for very large circuits
921                if characteristics.num_qubits >= 30 {
922                    score += 0.5;
923                }
924                if characteristics.memory_requirement > self.config.memory_budget / 2 {
925                    score += 0.3;
926                }
927            }
928            BackendType::Auto => {
929                // Fallback case
930                score = 0.1;
931            }
932        }
933
934        score.min(1.0)
935    }
936
937    /// Generate recommendation reasoning text
938    fn generate_recommendation_reasoning(
939        &self,
940        backend_type: BackendType,
941        characteristics: &CircuitCharacteristics,
942    ) -> String {
943        match backend_type {
944            BackendType::StateVector => {
945                format!("CPU state vector simulator recommended for {} qubits, {} gates. Suitable for small circuits with straightforward execution.",
946                       characteristics.num_qubits, characteristics.num_gates)
947            }
948            BackendType::SciRS2Gpu => {
949                format!("SciRS2 GPU simulator recommended for {} qubits, {} gates. High parallelism potential ({:.2}) and two-qubit gate density ({:.2}) make GPU acceleration beneficial.",
950                       characteristics.num_qubits, characteristics.num_gates, characteristics.parallelism_potential, characteristics.two_qubit_density)
951            }
952            BackendType::LargeScale => {
953                format!("Large-scale simulator recommended for {} qubits, {} gates. Circuit complexity ({:.2}) and depth ({}) require optimized memory management.",
954                       characteristics.num_qubits, characteristics.num_gates, characteristics.complexity_score, characteristics.circuit_depth)
955            }
956            BackendType::Distributed => {
957                format!("Distributed simulator recommended for {} qubits, {} gates. Memory requirement ({:.1} MB) exceeds single-node capacity.",
958                       characteristics.num_qubits, characteristics.num_gates, characteristics.memory_requirement as f64 / (1024.0 * 1024.0))
959            }
960            BackendType::Auto => "Automatic backend selection".to_string(),
961        }
962    }
963
964    /// Estimate execution time for backend and characteristics
965    fn estimate_execution_time(
966        &self,
967        backend_type: BackendType,
968        characteristics: &CircuitCharacteristics,
969    ) -> Duration {
970        let base_time_ms = match backend_type {
971            BackendType::StateVector => characteristics.num_gates as u64 * 10,
972            BackendType::SciRS2Gpu => characteristics.num_gates as u64 * 2,
973            BackendType::LargeScale => characteristics.num_gates as u64 * 5,
974            BackendType::Distributed => characteristics.num_gates as u64 * 15,
975            BackendType::Auto => characteristics.num_gates as u64 * 10,
976        };
977
978        // Apply complexity factor
979        let complexity_factor = characteristics.complexity_score.mul_add(2.0, 1.0) as u64;
980        Duration::from_millis(base_time_ms * complexity_factor)
981    }
982
983    /// Execute circuit with specified backend
984    fn execute_with_backend<const N: usize>(
985        &self,
986        circuit: &Circuit<N>,
987        backend_type: BackendType,
988    ) -> Result<Register<N>> {
989        match backend_type {
990            BackendType::StateVector => {
991                let simulator = StateVectorSimulator::new();
992                simulator
993                    .run(circuit)
994                    .map_err(|e| SimulatorError::ComputationError(e.to_string()))
995                    .and_then(|result| {
996                        Register::with_amplitudes(result.amplitudes().to_vec())
997                            .map_err(|e| SimulatorError::ComputationError(e.to_string()))
998                    })
999            }
1000            BackendType::SciRS2Gpu => {
1001                #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1002                {
1003                    let mut simulator = SciRS2GpuStateVectorSimulator::new()
1004                        .map_err(|e| SimulatorError::ComputationError(e.to_string()))?;
1005                    use crate::simulator::Simulator;
1006                    simulator
1007                        .run(circuit)
1008                        .map_err(|e| SimulatorError::ComputationError(e.to_string()))
1009                        .and_then(|result| {
1010                            Register::with_amplitudes(result.amplitudes().to_vec())
1011                                .map_err(|e| SimulatorError::ComputationError(e.to_string()))
1012                        })
1013                }
1014                #[cfg(any(not(feature = "gpu"), target_os = "macos"))]
1015                {
1016                    // Fallback to state vector if GPU not available
1017                    let simulator = StateVectorSimulator::new();
1018                    simulator
1019                        .run(circuit)
1020                        .map_err(|e| SimulatorError::ComputationError(e.to_string()))
1021                        .and_then(|result| {
1022                            Register::with_amplitudes(result.amplitudes().to_vec())
1023                                .map_err(|e| SimulatorError::ComputationError(e.to_string()))
1024                        })
1025                }
1026            }
1027            BackendType::LargeScale => {
1028                // Create large-scale simulator with optimized configuration
1029                let config = LargeScaleSimulatorConfig::default();
1030                let simulator = LargeScaleQuantumSimulator::new(config)
1031                    .map_err(|e| SimulatorError::ComputationError(e.to_string()))?;
1032                simulator
1033                    .run(circuit)
1034                    .map_err(|e| SimulatorError::ComputationError(e.to_string()))
1035            }
1036            BackendType::Distributed => {
1037                // Fallback to large-scale for now
1038                let config = LargeScaleSimulatorConfig::default();
1039                let simulator = LargeScaleQuantumSimulator::new(config)
1040                    .map_err(|e| SimulatorError::ComputationError(e.to_string()))?;
1041                simulator
1042                    .run(circuit)
1043                    .map_err(|e| SimulatorError::ComputationError(e.to_string()))
1044            }
1045            BackendType::Auto => {
1046                // This should not happen, but fallback to state vector
1047                let simulator = StateVectorSimulator::new();
1048                simulator
1049                    .run(circuit)
1050                    .map_err(|e| SimulatorError::ComputationError(e.to_string()))
1051            }
1052        }
1053    }
1054
1055    /// Convert Register to `SimulatorResult`
1056    fn register_to_simulator_result<const N: usize>(
1057        &self,
1058        register: Register<N>,
1059    ) -> SimulatorResult<N> {
1060        // Extract amplitudes from register
1061        let amplitudes = register.amplitudes().to_vec();
1062
1063        SimulatorResult {
1064            amplitudes,
1065            num_qubits: N,
1066        }
1067    }
1068
1069    /// Record performance metrics for future optimization
1070    fn record_performance_metrics<const N: usize>(
1071        &mut self,
1072        circuit: &Circuit<N>,
1073        backend_type: BackendType,
1074        execution_time: Duration,
1075    ) {
1076        // Exact dense state-vector footprint for an N-qubit circuit. This is the
1077        // dominant memory cost of state-vector simulation and is a deterministic
1078        // function of the qubit count, not a profiler reading.
1079        let state_vector_bytes = (1usize << N) * std::mem::size_of::<Complex64>();
1080
1081        let elapsed_secs = execution_time.as_secs_f64();
1082        let throughput = if elapsed_secs > 0.0 {
1083            circuit.num_gates() as f64 / elapsed_secs
1084        } else {
1085            0.0
1086        };
1087
1088        let metrics = PerformanceMetrics {
1089            execution_time,
1090            memory_usage: state_vector_bytes,
1091            // In-process CPU utilization is unavailable without OS sampling.
1092            cpu_utilization: None,
1093            gpu_utilization: None,
1094            throughput,
1095            // No error model was applied on this exact-simulation path.
1096            error_rate: None,
1097        };
1098
1099        // Store the coarse characteristics bucket so the cache lookup in
1100        // `are_characteristics_similar` can match comparable circuits, rather
1101        // than a per-gate hash that almost never repeats.
1102        let circuit_hash = match self.analyze_circuit(circuit) {
1103            Ok(characteristics) => self.characteristics_bucket_hash(&characteristics),
1104            Err(_) => self.compute_circuit_hash(circuit),
1105        };
1106
1107        let history_entry = PerformanceHistory {
1108            circuit_hash,
1109            backend_type,
1110            metrics,
1111            timestamp: Instant::now(),
1112        };
1113
1114        self.performance_cache.push(history_entry);
1115
1116        // Maintain cache size limit
1117        if self.performance_cache.len() > self.config.performance_cache_size {
1118            self.performance_cache.remove(0);
1119        }
1120    }
1121
1122    /// Compute hash for circuit caching
1123    fn compute_circuit_hash<const N: usize>(&self, circuit: &Circuit<N>) -> u64 {
1124        use std::collections::hash_map::DefaultHasher;
1125        use std::hash::{Hash, Hasher};
1126
1127        let mut hasher = DefaultHasher::new();
1128        circuit.num_gates().hash(&mut hasher);
1129        circuit.num_qubits().hash(&mut hasher);
1130
1131        for gate in circuit.gates() {
1132            gate.name().hash(&mut hasher);
1133            gate.qubits().len().hash(&mut hasher);
1134        }
1135
1136        hasher.finish()
1137    }
1138
1139    /// Get human-readable backend type name
1140    const fn backend_type_name(&self, backend_type: BackendType) -> &'static str {
1141        match backend_type {
1142            BackendType::StateVector => "CPU StateVector",
1143            BackendType::SciRS2Gpu => "SciRS2 GPU",
1144            BackendType::LargeScale => "Large-Scale",
1145            BackendType::Distributed => "Distributed",
1146            BackendType::Auto => "Auto",
1147        }
1148    }
1149
1150    /// Get optimization statistics
1151    #[must_use]
1152    pub fn get_performance_summary(&self) -> String {
1153        let total_circuits = self.performance_cache.len();
1154        if total_circuits == 0 {
1155            return "No performance data available".to_string();
1156        }
1157
1158        let avg_execution_time = self
1159            .performance_cache
1160            .iter()
1161            .map(|entry| entry.metrics.execution_time.as_millis())
1162            .sum::<u128>()
1163            / total_circuits as u128;
1164
1165        let backend_usage: HashMap<BackendType, usize> =
1166            self.performance_cache
1167                .iter()
1168                .fold(HashMap::new(), |mut acc, entry| {
1169                    *acc.entry(entry.backend_type).or_insert(0) += 1;
1170                    acc
1171                });
1172
1173        let mut summary = "AutoOptimizer Performance Summary\n".to_string();
1174        writeln!(summary, "Total circuits processed: {total_circuits}")
1175            .expect("Writing to String should never fail");
1176        writeln!(summary, "Average execution time: {avg_execution_time}ms")
1177            .expect("Writing to String should never fail");
1178        summary.push_str("Backend usage:\n");
1179
1180        for (backend, count) in backend_usage {
1181            let percentage = (count as f64 / total_circuits as f64) * 100.0;
1182            writeln!(
1183                summary,
1184                "  {}: {} ({:.1}%)",
1185                self.backend_type_name(backend),
1186                count,
1187                percentage
1188            )
1189            .expect("Writing to String should never fail");
1190        }
1191
1192        summary
1193    }
1194}
1195
1196impl Default for AutoOptimizer {
1197    fn default() -> Self {
1198        Self::new()
1199    }
1200}
1201
1202impl SciRS2CircuitAnalyzer {
1203    /// Compute a normalized structural-richness score for a circuit in `[0, 1]`.
1204    ///
1205    /// This is a real function of the circuit, combining three measured ratios:
1206    /// gate utilization (gates per qubit, saturating), two-qubit-gate fraction,
1207    /// and average gate arity normalized by the qubit count. Circuits that touch
1208    /// more qubits with more entangling, higher-arity gates score higher. When
1209    /// advanced features are disabled the score is reported as `0.0` (analysis
1210    /// not performed) rather than a fabricated value.
1211    fn analyze_circuit_with_scirs2<const N: usize>(
1212        &self,
1213        circuit: &Circuit<N>,
1214    ) -> QuantRS2Result<f64> {
1215        if !self.enable_advanced_features {
1216            return Ok(0.0);
1217        }
1218
1219        let num_gates = circuit.num_gates();
1220        if num_gates == 0 || N == 0 {
1221            return Ok(0.0);
1222        }
1223
1224        // Gate utilization: how densely gates are packed relative to qubits,
1225        // squashed into [0, 1) with a smooth saturating curve.
1226        let gates_per_qubit = num_gates as f64 / N as f64;
1227        let utilization = gates_per_qubit / (1.0 + gates_per_qubit);
1228
1229        // Two-qubit (entangling) gate fraction.
1230        let two_qubit_gates = circuit
1231            .gates()
1232            .iter()
1233            .filter(|gate| gate.qubits().len() >= 2)
1234            .count();
1235        let entangling_fraction = two_qubit_gates as f64 / num_gates as f64;
1236
1237        // Average gate arity normalized by the qubit count.
1238        let total_arity: usize = circuit.gates().iter().map(|gate| gate.qubits().len()).sum();
1239        let mean_arity = total_arity as f64 / num_gates as f64;
1240        let arity_score = (mean_arity / N as f64).min(1.0);
1241
1242        // Equal-weight blend of the three real structural signals.
1243        Ok(((utilization + entangling_fraction + arity_score) / 3.0).clamp(0.0, 1.0))
1244    }
1245}
1246
1247/// Convenience function to execute a circuit with automatic optimization
1248pub fn execute_with_auto_optimization<const N: usize>(
1249    circuit: &Circuit<N>,
1250) -> Result<SimulatorResult<N>> {
1251    let mut optimizer = AutoOptimizer::new();
1252    optimizer.execute_optimized(circuit)
1253}
1254
1255/// Convenience function to get backend recommendation for a circuit
1256pub fn recommend_backend_for_circuit<const N: usize>(
1257    circuit: &Circuit<N>,
1258) -> QuantRS2Result<BackendRecommendation> {
1259    let mut optimizer = AutoOptimizer::new();
1260    optimizer.recommend_backend(circuit)
1261}
1262
1263#[cfg(test)]
1264mod tests {
1265    use super::*;
1266    use quantrs2_circuit::builder::CircuitBuilder;
1267
1268    #[test]
1269    fn test_auto_optimizer_creation() {
1270        let optimizer = AutoOptimizer::new();
1271        assert!(optimizer.config.enable_profiling);
1272    }
1273
1274    #[test]
1275    fn test_circuit_characteristics_analysis() {
1276        let optimizer = AutoOptimizer::new();
1277
1278        // Create a simple test circuit
1279        let mut builder = CircuitBuilder::<4>::new();
1280        let _ = builder.h(0);
1281        let _ = builder.cnot(0, 1);
1282        let _ = builder.h(2);
1283        let _ = builder.cnot(2, 3);
1284        let circuit = builder.build();
1285
1286        let characteristics = optimizer
1287            .analyze_circuit(&circuit)
1288            .expect("Failed to analyze circuit characteristics");
1289
1290        assert_eq!(characteristics.num_qubits, 4);
1291        assert_eq!(characteristics.num_gates, 4);
1292        assert!(characteristics.circuit_depth > 0);
1293        assert!(characteristics.two_qubit_density > 0.0);
1294    }
1295
1296    #[test]
1297    fn test_backend_recommendation() {
1298        let mut optimizer = AutoOptimizer::new();
1299
1300        // Create a small circuit
1301        let mut builder = CircuitBuilder::<2>::new();
1302        let _ = builder.h(0);
1303        let _ = builder.cnot(0, 1);
1304        let circuit = builder.build();
1305
1306        let recommendation = optimizer
1307            .recommend_backend(&circuit)
1308            .expect("Failed to get backend recommendation");
1309
1310        assert!(recommendation.confidence > 0.0);
1311        assert!(!recommendation.reasoning.is_empty());
1312    }
1313
1314    #[test]
1315    fn test_execute_with_optimization() {
1316        let mut optimizer = AutoOptimizer::new();
1317
1318        // Create a simple circuit
1319        let mut builder = CircuitBuilder::<2>::new();
1320        let _ = builder.h(0);
1321        let _ = builder.cnot(0, 1);
1322        let circuit = builder.build();
1323
1324        let result = optimizer.execute_optimized(&circuit);
1325        assert!(result.is_ok());
1326
1327        if let Ok(sim_result) = result {
1328            assert_eq!(sim_result.num_qubits, 2);
1329            assert_eq!(sim_result.amplitudes.len(), 4);
1330        }
1331    }
1332
1333    #[test]
1334    fn test_convenience_functions() {
1335        // Create a simple circuit
1336        let mut builder = CircuitBuilder::<2>::new();
1337        let _ = builder.h(0);
1338        let _ = builder.cnot(0, 1);
1339        let circuit = builder.build();
1340
1341        // Test recommendation function
1342        let recommendation = recommend_backend_for_circuit(&circuit);
1343        assert!(recommendation.is_ok());
1344
1345        // Test execution function
1346        let result = execute_with_auto_optimization(&circuit);
1347        assert!(result.is_ok());
1348    }
1349}