quantrs2_core/optimization/
lazy_evaluation.rs

1//! Lazy evaluation system for gate sequence optimization
2//!
3//! This module provides a lazy evaluation framework that defers gate optimizations
4//! until they're actually needed, improving performance for large circuits by avoiding
5//! unnecessary computation and enabling more sophisticated optimization strategies.
6
7use crate::{
8    error::{QuantRS2Error, QuantRS2Result},
9    gate::GateOp,
10    optimization::OptimizationChain,
11    qubit::QubitId,
12};
13use serde::{Deserialize, Serialize};
14use std::{
15    collections::{HashMap, HashSet, VecDeque},
16    sync::{Arc, RwLock},
17    time::{Duration, Instant},
18};
19
20/// Configuration for lazy evaluation
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct LazyEvaluationConfig {
23    /// Maximum number of gates to buffer before forced evaluation
24    pub max_buffer_size: usize,
25    /// Maximum time to defer evaluation
26    pub max_defer_time: Duration,
27    /// Enable dependency-based optimization ordering
28    pub enable_dependency_optimization: bool,
29    /// Enable speculative optimization
30    pub enable_speculative_optimization: bool,
31    /// Number of worker threads for async optimization
32    pub num_optimization_threads: usize,
33    /// Cache size for optimization results
34    pub optimization_cache_size: usize,
35}
36
37impl Default for LazyEvaluationConfig {
38    fn default() -> Self {
39        Self {
40            max_buffer_size: 1000,
41            max_defer_time: Duration::from_millis(100),
42            enable_dependency_optimization: true,
43            enable_speculative_optimization: true,
44            num_optimization_threads: 4,
45            optimization_cache_size: 10000,
46        }
47    }
48}
49
50/// Lazy evaluation context for a gate
51#[derive(Debug, Clone)]
52pub struct LazyGateContext {
53    /// Unique identifier for this gate in the pipeline
54    pub gate_id: usize,
55    /// The gate to be optimized
56    pub gate: Box<dyn GateOp>,
57    /// Dependencies (other gates that must be evaluated first)
58    pub dependencies: HashSet<usize>,
59    /// Dependents (gates that depend on this one)
60    pub dependents: HashSet<usize>,
61    /// Priority for evaluation (higher = more urgent)
62    pub priority: f64,
63    /// Timestamp when gate was added
64    pub created_at: Instant,
65    /// Whether this gate has been evaluated
66    pub is_evaluated: bool,
67    /// Cached optimization result
68    pub cached_result: Option<OptimizationResult>,
69}
70
71/// Result of a lazy optimization
72#[derive(Debug, Clone)]
73pub struct OptimizationResult {
74    /// Optimized gate sequence
75    pub optimized_gates: Vec<Box<dyn GateOp>>,
76    /// Optimization statistics
77    pub stats: OptimizationStats,
78    /// Time taken for optimization
79    pub optimization_time: Duration,
80}
81
82/// Statistics from optimization
83#[derive(Debug, Clone, Default)]
84pub struct OptimizationStats {
85    /// Number of gates before optimization
86    pub gates_before: usize,
87    /// Number of gates after optimization
88    pub gates_after: usize,
89    /// Number of optimization passes applied
90    pub passes_applied: usize,
91    /// Estimated performance improvement
92    pub performance_improvement: f64,
93    /// Memory savings achieved
94    pub memory_savings: usize,
95}
96
97/// Lazy optimization pipeline
98pub struct LazyOptimizationPipeline {
99    /// Configuration
100    config: LazyEvaluationConfig,
101    /// Buffered gates awaiting optimization
102    gate_buffer: Arc<RwLock<HashMap<usize, LazyGateContext>>>,
103    /// Dependency graph
104    dependency_graph: Arc<RwLock<DependencyGraph>>,
105    /// Optimization chain to apply
106    optimization_chain: OptimizationChain,
107    /// Compilation cache for optimized results
108    optimization_cache: Arc<RwLock<OptimizationCache>>,
109    /// Next gate ID
110    next_gate_id: Arc<RwLock<usize>>,
111    /// Worker thread handles
112    worker_handles: Vec<std::thread::JoinHandle<()>>,
113    /// Shutdown signal
114    shutdown_signal: Arc<RwLock<bool>>,
115}
116
117/// Dependency graph for managing gate relationships
118#[derive(Debug, Default)]
119struct DependencyGraph {
120    /// Adjacency list representation
121    edges: HashMap<usize, HashSet<usize>>,
122    /// Reverse edges for quick lookup
123    reverse_edges: HashMap<usize, HashSet<usize>>,
124    /// Topological ordering cache
125    topo_order_cache: Option<Vec<usize>>,
126}
127
128/// Cache for optimization results
129struct OptimizationCache {
130    /// Cache entries indexed by gate hash
131    entries: HashMap<u64, CachedOptimization>,
132    /// LRU queue for eviction
133    lru_queue: VecDeque<u64>,
134    /// Maximum cache size
135    max_size: usize,
136}
137
138/// Cached optimization entry
139#[derive(Debug, Clone)]
140struct CachedOptimization {
141    /// The optimization result
142    result: OptimizationResult,
143    /// Access count for LRU
144    access_count: usize,
145    /// Last access time
146    last_accessed: Instant,
147}
148
149impl LazyOptimizationPipeline {
150    /// Create a new lazy optimization pipeline
151    pub fn new(
152        config: LazyEvaluationConfig,
153        optimization_chain: OptimizationChain,
154    ) -> QuantRS2Result<Self> {
155        let gate_buffer = Arc::new(RwLock::new(HashMap::new()));
156        let dependency_graph = Arc::new(RwLock::new(DependencyGraph::default()));
157        let optimization_cache = Arc::new(RwLock::new(OptimizationCache::new(
158            config.optimization_cache_size,
159        )));
160        let next_gate_id = Arc::new(RwLock::new(0));
161        let shutdown_signal = Arc::new(RwLock::new(false));
162
163        // Start worker threads
164        let mut worker_handles = Vec::new();
165        for worker_id in 0..config.num_optimization_threads {
166            let handle = Self::start_worker_thread(
167                worker_id,
168                Arc::clone(&gate_buffer),
169                Arc::clone(&dependency_graph),
170                Arc::clone(&optimization_cache),
171                Arc::clone(&shutdown_signal),
172                config.clone(),
173            );
174            worker_handles.push(handle);
175        }
176
177        Ok(Self {
178            config,
179            gate_buffer,
180            dependency_graph,
181            optimization_chain,
182            optimization_cache,
183            next_gate_id,
184            worker_handles,
185            shutdown_signal,
186        })
187    }
188
189    /// Add a gate to the lazy evaluation pipeline
190    pub fn add_gate(&self, gate: Box<dyn GateOp>) -> QuantRS2Result<usize> {
191        let gate_id = {
192            let mut next_id = self
193                .next_gate_id
194                .write()
195                .map_err(|_| QuantRS2Error::RuntimeError("Gate ID lock poisoned".to_string()))?;
196            let id = *next_id;
197            *next_id += 1;
198            id
199        };
200
201        // Analyze dependencies based on qubit overlap
202        let dependencies = self.analyze_dependencies(gate.as_ref())?;
203
204        // Calculate priority based on gate type and dependencies
205        let priority = self.calculate_priority(gate.as_ref(), &dependencies);
206
207        let context = LazyGateContext {
208            gate_id,
209            gate,
210            dependencies: dependencies.clone(),
211            dependents: HashSet::new(),
212            priority,
213            created_at: Instant::now(),
214            is_evaluated: false,
215            cached_result: None,
216        };
217
218        // Update dependency graph
219        {
220            let mut graph = self.dependency_graph.write().map_err(|_| {
221                QuantRS2Error::RuntimeError("Dependency graph lock poisoned".to_string())
222            })?;
223            graph.add_gate(gate_id, dependencies);
224        }
225
226        // Add to buffer
227        {
228            let mut buffer = self.gate_buffer.write().map_err(|_| {
229                QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string())
230            })?;
231            buffer.insert(gate_id, context);
232        }
233
234        // Check if we need to force evaluation due to buffer size or time
235        self.check_forced_evaluation()?;
236
237        Ok(gate_id)
238    }
239
240    /// Evaluate a specific gate (force optimization)
241    pub fn evaluate_gate(&self, gate_id: usize) -> QuantRS2Result<OptimizationResult> {
242        // Check cache first
243        if let Some(cached) = self.get_cached_result(gate_id)? {
244            return Ok(cached);
245        }
246
247        // Get the gate context
248        let context = {
249            let buffer = self.gate_buffer.read().map_err(|_| {
250                QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string())
251            })?;
252            buffer.get(&gate_id).cloned().ok_or_else(|| {
253                QuantRS2Error::InvalidInput(format!("Gate {gate_id} not found in buffer"))
254            })?
255        };
256
257        // Ensure dependencies are evaluated first
258        self.evaluate_dependencies(&context.dependencies)?;
259
260        // Perform the optimization
261        let result = self.optimize_gate_context(&context)?;
262
263        // Cache the result
264        self.cache_optimization_result(gate_id, &result)?;
265
266        // Mark as evaluated
267        {
268            let mut buffer = self.gate_buffer.write().map_err(|_| {
269                QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string())
270            })?;
271            if let Some(ctx) = buffer.get_mut(&gate_id) {
272                ctx.is_evaluated = true;
273                ctx.cached_result = Some(result.clone());
274            }
275        }
276
277        Ok(result)
278    }
279
280    /// Evaluate all buffered gates
281    pub fn evaluate_all(&self) -> QuantRS2Result<Vec<OptimizationResult>> {
282        // Get topological ordering of gates
283        let ordered_gates = {
284            let graph = self.dependency_graph.read().map_err(|_| {
285                QuantRS2Error::RuntimeError("Dependency graph lock poisoned".to_string())
286            })?;
287            graph.topological_sort()
288        };
289
290        let mut results = Vec::new();
291        for gate_id in ordered_gates {
292            if let Ok(result) = self.evaluate_gate(gate_id) {
293                results.push(result);
294            }
295        }
296
297        // Clear the buffer
298        {
299            if let Ok(mut buffer) = self.gate_buffer.write() {
300                buffer.clear();
301            }
302        }
303
304        Ok(results)
305    }
306
307    /// Get optimization statistics
308    pub fn get_statistics(&self) -> LazyEvaluationStats {
309        let buffer = self.gate_buffer.read().ok();
310        let cache = self.optimization_cache.read().ok();
311
312        let (total_gates, evaluated_gates) = buffer
313            .as_ref()
314            .map(|b| {
315                let total = b.len();
316                let evaluated = b.values().filter(|ctx| ctx.is_evaluated).count();
317                (total, evaluated)
318            })
319            .unwrap_or((0, 0));
320        let pending_gates = total_gates - evaluated_gates;
321
322        let (cache_hits, cache_size, avg_time) = cache
323            .as_ref()
324            .map(|c| {
325                (
326                    c.get_hit_count(),
327                    c.entries.len(),
328                    c.get_average_optimization_time(),
329                )
330            })
331            .unwrap_or((0, 0, Duration::ZERO));
332
333        LazyEvaluationStats {
334            total_gates,
335            evaluated_gates,
336            pending_gates,
337            cache_hits,
338            cache_size,
339            average_optimization_time: avg_time,
340        }
341    }
342
343    /// Analyze dependencies for a gate based on qubit overlap
344    fn analyze_dependencies(&self, gate: &dyn GateOp) -> QuantRS2Result<HashSet<usize>> {
345        let gate_qubits: HashSet<QubitId> = gate.qubits().into_iter().collect();
346        let mut dependencies = HashSet::new();
347
348        let buffer = self
349            .gate_buffer
350            .read()
351            .map_err(|_| QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string()))?;
352        for (gate_id, context) in buffer.iter() {
353            let context_qubits: HashSet<QubitId> = context.gate.qubits().into_iter().collect();
354
355            // If there's qubit overlap, this gate depends on the previous one
356            if !gate_qubits.is_disjoint(&context_qubits) {
357                dependencies.insert(*gate_id);
358            }
359        }
360
361        Ok(dependencies)
362    }
363
364    /// Calculate priority for a gate
365    fn calculate_priority(&self, gate: &dyn GateOp, dependencies: &HashSet<usize>) -> f64 {
366        let mut priority = 0.0;
367
368        // Higher priority for gates with fewer qubits (simpler to optimize)
369        priority += 10.0 / (gate.num_qubits() as f64 + 1.0);
370
371        // Lower priority for gates with many dependencies
372        priority -= dependencies.len() as f64 * 0.5;
373
374        // Higher priority for common gate types
375        match gate.name() {
376            "H" | "X" | "Y" | "Z" => priority += 5.0,
377            "CNOT" | "CZ" => priority += 3.0,
378            "RX" | "RY" | "RZ" => priority += 2.0,
379            _ => priority += 1.0,
380        }
381
382        priority.max(0.1)
383    }
384
385    /// Check if forced evaluation is needed
386    fn check_forced_evaluation(&self) -> QuantRS2Result<()> {
387        let buffer = self
388            .gate_buffer
389            .read()
390            .map_err(|_| QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string()))?;
391
392        // Check buffer size
393        if buffer.len() >= self.config.max_buffer_size {
394            drop(buffer);
395            return self.force_oldest_evaluation();
396        }
397
398        // Check time-based forced evaluation
399        let now = Instant::now();
400        for context in buffer.values() {
401            if now.duration_since(context.created_at) > self.config.max_defer_time {
402                drop(buffer);
403                return self.force_oldest_evaluation();
404            }
405        }
406
407        Ok(())
408    }
409
410    /// Force evaluation of the oldest gate
411    fn force_oldest_evaluation(&self) -> QuantRS2Result<()> {
412        let oldest_gate_id = {
413            let buffer = self.gate_buffer.read().map_err(|_| {
414                QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string())
415            })?;
416            buffer
417                .values()
418                .filter(|ctx| !ctx.is_evaluated)
419                .min_by_key(|ctx| ctx.created_at)
420                .map(|ctx| ctx.gate_id)
421        };
422
423        if let Some(gate_id) = oldest_gate_id {
424            self.evaluate_gate(gate_id)?;
425        }
426
427        Ok(())
428    }
429
430    /// Evaluate dependencies recursively
431    fn evaluate_dependencies(&self, dependencies: &HashSet<usize>) -> QuantRS2Result<()> {
432        for &dep_id in dependencies {
433            if !self.is_gate_evaluated(dep_id) {
434                self.evaluate_gate(dep_id)?;
435            }
436        }
437        Ok(())
438    }
439
440    /// Check if a gate has been evaluated
441    fn is_gate_evaluated(&self, gate_id: usize) -> bool {
442        self.gate_buffer
443            .read()
444            .ok()
445            .and_then(|buffer| buffer.get(&gate_id).map(|ctx| ctx.is_evaluated))
446            .unwrap_or(false)
447    }
448
449    /// Optimize a gate context
450    fn optimize_gate_context(
451        &self,
452        context: &LazyGateContext,
453    ) -> QuantRS2Result<OptimizationResult> {
454        let start_time = Instant::now();
455
456        // Apply optimization chain
457        let input_gates = vec![context.gate.clone_gate()];
458        let optimized_gates = self.optimization_chain.optimize(input_gates)?;
459
460        let optimization_time = start_time.elapsed();
461
462        // Calculate statistics
463        let stats = OptimizationStats {
464            gates_before: 1,
465            gates_after: optimized_gates.len(),
466            passes_applied: 1, // Would track actual passes in real implementation
467            performance_improvement: self.estimate_performance_improvement(&optimized_gates),
468            memory_savings: self.estimate_memory_savings(&optimized_gates),
469        };
470
471        Ok(OptimizationResult {
472            optimized_gates,
473            stats,
474            optimization_time,
475        })
476    }
477
478    /// Estimate performance improvement from optimization
479    fn estimate_performance_improvement(&self, gates: &[Box<dyn GateOp>]) -> f64 {
480        // Simple heuristic: fewer gates = better performance
481        let base_improvement = 1.0 / (gates.len() as f64 + 1.0);
482
483        // Bonus for single-qubit gates
484        let single_qubit_bonus = gates.iter().filter(|g| g.num_qubits() == 1).count() as f64 * 0.1;
485
486        base_improvement + single_qubit_bonus
487    }
488
489    /// Estimate memory savings from optimization
490    fn estimate_memory_savings(&self, gates: &[Box<dyn GateOp>]) -> usize {
491        // Simple heuristic based on gate complexity
492        gates
493            .iter()
494            .map(|g| match g.num_qubits() {
495                1 => 16,                  // 2x2 complex matrix
496                2 => 64,                  // 4x4 complex matrix
497                n => (1 << (2 * n)) * 16, // 2^n x 2^n complex matrix
498            })
499            .sum()
500    }
501
502    /// Get cached optimization result
503    fn get_cached_result(&self, gate_id: usize) -> QuantRS2Result<Option<OptimizationResult>> {
504        let buffer = self
505            .gate_buffer
506            .read()
507            .map_err(|_| QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string()))?;
508        if let Some(context) = buffer.get(&gate_id) {
509            if let Some(ref result) = context.cached_result {
510                return Ok(Some(result.clone()));
511            }
512        }
513        drop(buffer);
514
515        // Check optimization cache
516        let gate_hash = self.compute_gate_hash(gate_id)?;
517        let mut cache = self.optimization_cache.write().map_err(|_| {
518            QuantRS2Error::RuntimeError("Optimization cache lock poisoned".to_string())
519        })?;
520        if let Some(cached) = cache.get_mut(gate_hash) {
521            return Ok(Some(cached.result.clone()));
522        }
523
524        Ok(None)
525    }
526
527    /// Cache optimization result
528    fn cache_optimization_result(
529        &self,
530        gate_id: usize,
531        result: &OptimizationResult,
532    ) -> QuantRS2Result<()> {
533        let gate_hash = self.compute_gate_hash(gate_id)?;
534        let mut cache = self.optimization_cache.write().map_err(|_| {
535            QuantRS2Error::RuntimeError("Optimization cache lock poisoned".to_string())
536        })?;
537
538        let cached = CachedOptimization {
539            result: result.clone(),
540            access_count: 1,
541            last_accessed: Instant::now(),
542        };
543
544        cache.insert(gate_hash, cached);
545        Ok(())
546    }
547
548    /// Compute hash for a gate
549    fn compute_gate_hash(&self, gate_id: usize) -> QuantRS2Result<u64> {
550        use std::collections::hash_map::DefaultHasher;
551        use std::hash::{Hash, Hasher};
552
553        let buffer = self
554            .gate_buffer
555            .read()
556            .map_err(|_| QuantRS2Error::RuntimeError("Gate buffer lock poisoned".to_string()))?;
557        let context = buffer
558            .get(&gate_id)
559            .ok_or_else(|| QuantRS2Error::InvalidInput(format!("Gate {gate_id} not found")))?;
560
561        let mut hasher = DefaultHasher::new();
562        context.gate.name().hash(&mut hasher);
563
564        // Hash the gate matrix
565        if let Ok(matrix) = context.gate.matrix() {
566            for elem in &matrix {
567                elem.re.to_bits().hash(&mut hasher);
568                elem.im.to_bits().hash(&mut hasher);
569            }
570        }
571
572        Ok(hasher.finish())
573    }
574
575    /// Start a worker thread for async optimization
576    fn start_worker_thread(
577        _worker_id: usize,
578        gate_buffer: Arc<RwLock<HashMap<usize, LazyGateContext>>>,
579        _dependency_graph: Arc<RwLock<DependencyGraph>>,
580        _optimization_cache: Arc<RwLock<OptimizationCache>>,
581        shutdown_signal: Arc<RwLock<bool>>,
582        config: LazyEvaluationConfig,
583    ) -> std::thread::JoinHandle<()> {
584        std::thread::spawn(move || {
585            let sleep_duration = Duration::from_millis(10);
586
587            loop {
588                // Check shutdown signal
589                {
590                    match shutdown_signal.read() {
591                        Ok(shutdown) if *shutdown => break,
592                        Err(_) => break, // Lock poisoned, exit gracefully
593                        _ => {}
594                    }
595                }
596
597                // Look for high-priority gates to optimize speculatively
598                if config.enable_speculative_optimization {
599                    let high_priority_gates = {
600                        match gate_buffer.read() {
601                            Ok(buffer) => buffer
602                                .values()
603                                .filter(|ctx| !ctx.is_evaluated && ctx.priority > 5.0)
604                                .map(|ctx| ctx.gate_id)
605                                .collect::<Vec<_>>(),
606                            Err(_) => continue, // Lock poisoned, skip iteration
607                        }
608                    };
609
610                    for gate_id in high_priority_gates {
611                        // This would require access to the pipeline's optimization methods
612                        // For now, just mark as processed
613                        if let Ok(mut buffer) = gate_buffer.write() {
614                            if let Some(ctx) = buffer.get_mut(&gate_id) {
615                                // Placeholder: would perform actual optimization here
616                                ctx.priority += 0.1; // Slight priority boost
617                            }
618                        }
619                    }
620                }
621
622                std::thread::sleep(sleep_duration);
623            }
624        })
625    }
626}
627
628impl Drop for LazyOptimizationPipeline {
629    fn drop(&mut self) {
630        // Signal shutdown to worker threads
631        {
632            if let Ok(mut shutdown) = self.shutdown_signal.write() {
633                *shutdown = true;
634            }
635        }
636
637        // Wait for all worker threads to finish
638        while let Some(handle) = self.worker_handles.pop() {
639            let _ = handle.join();
640        }
641    }
642}
643
644impl DependencyGraph {
645    /// Add a gate with its dependencies
646    fn add_gate(&mut self, gate_id: usize, dependencies: HashSet<usize>) {
647        self.edges.insert(gate_id, dependencies.clone());
648
649        // Update reverse edges
650        for dep in dependencies {
651            self.reverse_edges
652                .entry(dep)
653                .or_insert_with(HashSet::new)
654                .insert(gate_id);
655        }
656
657        // Invalidate topological order cache
658        self.topo_order_cache = None;
659    }
660
661    /// Get topological ordering of gates
662    fn topological_sort(&self) -> Vec<usize> {
663        if let Some(ref cached) = self.topo_order_cache {
664            return cached.clone();
665        }
666
667        let mut result = Vec::new();
668        let mut in_degree: HashMap<usize, usize> = HashMap::new();
669        let mut queue = VecDeque::new();
670
671        // Calculate in-degrees
672        for (&node, edges) in &self.edges {
673            in_degree.entry(node).or_insert(0);
674            for &dep in edges {
675                *in_degree.entry(dep).or_insert(0) += 1;
676            }
677        }
678
679        // Find nodes with no incoming edges
680        for (&node, &degree) in &in_degree {
681            if degree == 0 {
682                queue.push_back(node);
683            }
684        }
685
686        // Process nodes
687        while let Some(node) = queue.pop_front() {
688            result.push(node);
689
690            if let Some(dependents) = self.reverse_edges.get(&node) {
691                for &dependent in dependents {
692                    if let Some(degree) = in_degree.get_mut(&dependent) {
693                        *degree -= 1;
694                        if *degree == 0 {
695                            queue.push_back(dependent);
696                        }
697                    }
698                }
699            }
700        }
701
702        result
703    }
704}
705
706impl OptimizationCache {
707    fn new(max_size: usize) -> Self {
708        Self {
709            entries: HashMap::new(),
710            lru_queue: VecDeque::new(),
711            max_size,
712        }
713    }
714
715    fn get_mut(&mut self, hash: u64) -> Option<&mut CachedOptimization> {
716        if let Some(cached) = self.entries.get_mut(&hash) {
717            cached.access_count += 1;
718            cached.last_accessed = Instant::now();
719
720            // Update LRU
721            self.lru_queue.retain(|&h| h != hash);
722            self.lru_queue.push_front(hash);
723
724            Some(cached)
725        } else {
726            None
727        }
728    }
729
730    fn insert(&mut self, hash: u64, cached: CachedOptimization) {
731        // Evict if necessary
732        while self.entries.len() >= self.max_size {
733            if let Some(oldest_hash) = self.lru_queue.pop_back() {
734                self.entries.remove(&oldest_hash);
735            } else {
736                break;
737            }
738        }
739
740        self.entries.insert(hash, cached);
741        self.lru_queue.push_front(hash);
742    }
743
744    fn get_hit_count(&self) -> usize {
745        self.entries.values().map(|c| c.access_count).sum()
746    }
747
748    fn get_average_optimization_time(&self) -> Duration {
749        if self.entries.is_empty() {
750            return Duration::ZERO;
751        }
752
753        let total_time: Duration = self
754            .entries
755            .values()
756            .map(|c| c.result.optimization_time)
757            .sum();
758
759        total_time / self.entries.len() as u32
760    }
761}
762
763/// Statistics for lazy evaluation
764#[derive(Debug, Clone)]
765pub struct LazyEvaluationStats {
766    pub total_gates: usize,
767    pub evaluated_gates: usize,
768    pub pending_gates: usize,
769    pub cache_hits: usize,
770    pub cache_size: usize,
771    pub average_optimization_time: Duration,
772}
773
774#[cfg(test)]
775mod tests {
776    use super::*;
777    use crate::gate::single::{Hadamard, PauliX, PauliZ};
778    use crate::optimization::OptimizationChain;
779
780    #[test]
781    fn test_lazy_pipeline_creation() {
782        let config = LazyEvaluationConfig::default();
783        let chain = OptimizationChain::new();
784
785        let pipeline =
786            LazyOptimizationPipeline::new(config, chain).expect("Failed to create pipeline");
787        let stats = pipeline.get_statistics();
788
789        assert_eq!(stats.total_gates, 0);
790        assert_eq!(stats.evaluated_gates, 0);
791    }
792
793    #[test]
794    fn test_gate_addition() {
795        let config = LazyEvaluationConfig::default();
796        let chain = OptimizationChain::new();
797
798        let pipeline =
799            LazyOptimizationPipeline::new(config, chain).expect("Failed to create pipeline");
800
801        let h_gate = Box::new(Hadamard {
802            target: crate::qubit::QubitId(0),
803        });
804        let gate_id = pipeline.add_gate(h_gate).expect("Failed to add gate");
805
806        assert_eq!(gate_id, 0);
807
808        let stats = pipeline.get_statistics();
809        assert_eq!(stats.total_gates, 1);
810        assert_eq!(stats.pending_gates, 1);
811    }
812
813    #[test]
814    #[ignore] // Intermittent multi-minute hangs in CI; cache priming causes excessive runtime.
815    fn test_gate_evaluation() {
816        let config = LazyEvaluationConfig::default();
817        let chain = OptimizationChain::new();
818
819        let pipeline =
820            LazyOptimizationPipeline::new(config, chain).expect("Failed to create pipeline");
821
822        let h_gate = Box::new(Hadamard {
823            target: crate::qubit::QubitId(0),
824        });
825        let gate_id = pipeline.add_gate(h_gate).expect("Failed to add gate");
826
827        let result = pipeline
828            .evaluate_gate(gate_id)
829            .expect("Failed to evaluate gate");
830        assert!(result.optimization_time > Duration::ZERO);
831
832        let stats = pipeline.get_statistics();
833        assert_eq!(stats.evaluated_gates, 1);
834        assert_eq!(stats.pending_gates, 0);
835    }
836
837    #[test]
838    fn test_dependency_analysis() {
839        let config = LazyEvaluationConfig::default();
840        let chain = OptimizationChain::new();
841
842        let pipeline =
843            LazyOptimizationPipeline::new(config, chain).expect("Failed to create pipeline");
844
845        // Add gates that share qubits
846        let h_gate = Box::new(Hadamard {
847            target: crate::qubit::QubitId(0),
848        });
849        let x_gate = Box::new(PauliX {
850            target: crate::qubit::QubitId(0),
851        });
852        let z_gate = Box::new(PauliZ {
853            target: crate::qubit::QubitId(1),
854        });
855
856        let _h_id = pipeline
857            .add_gate(h_gate)
858            .expect("Failed to add Hadamard gate");
859        let _x_id = pipeline
860            .add_gate(x_gate)
861            .expect("Failed to add PauliX gate");
862        let _z_id = pipeline
863            .add_gate(z_gate)
864            .expect("Failed to add PauliZ gate");
865
866        // X gate should depend on H gate (same qubit)
867        // Z gate should be independent (different qubit)
868
869        let results = pipeline
870            .evaluate_all()
871            .expect("Failed to evaluate all gates");
872        // Results may be filtered or combined during optimization
873        assert!(results.len() <= 3);
874    }
875
876    #[test]
877    #[ignore] // Slow test (>660s) - run explicitly with: cargo test -- --ignored
878    fn test_optimization_caching() {
879        let config = LazyEvaluationConfig::default();
880        let chain = OptimizationChain::new();
881
882        let pipeline =
883            LazyOptimizationPipeline::new(config, chain).expect("Failed to create pipeline");
884
885        let h_gate = Box::new(Hadamard {
886            target: crate::qubit::QubitId(0),
887        });
888        let gate_id = pipeline.add_gate(h_gate).expect("Failed to add gate");
889
890        // First evaluation
891        let result1 = pipeline
892            .evaluate_gate(gate_id)
893            .expect("Failed to evaluate gate first time");
894
895        // Second evaluation should use cache
896        let result2 = pipeline
897            .evaluate_gate(gate_id)
898            .expect("Failed to evaluate gate second time");
899
900        // Results should be identical
901        assert_eq!(result1.stats.gates_before, result2.stats.gates_before);
902        assert_eq!(result1.stats.gates_after, result2.stats.gates_after);
903    }
904}