Skip to main content

quantrs2_circuit/scirs2_cross_compilation_enhanced/
optimizers.rs

1//! ML-based optimization and compilation helpers
2//!
3//! This module contains the ML compilation optimizer, feature extractors,
4//! and internal helper types for cross-compilation.
5
6use super::config::{EnhancedCrossCompilationConfig, TargetPlatform};
7use super::types::{IRGate, IROperation, IROperationType, QuantumIR, SourceCircuit, TargetCode};
8use quantrs2_core::error::{QuantRS2Error, QuantRS2Result};
9use std::collections::HashMap;
10use std::f64::consts::PI;
11use std::sync::{Arc, Mutex};
12
13/// ML compilation optimizer
14pub struct MLCompilationOptimizer {
15    config: EnhancedCrossCompilationConfig,
16    model: Arc<Mutex<CompilationModel>>,
17    feature_extractor: Arc<CompilationFeatureExtractor>,
18}
19
20impl MLCompilationOptimizer {
21    pub fn new(config: EnhancedCrossCompilationConfig) -> Self {
22        Self {
23            config,
24            model: Arc::new(Mutex::new(CompilationModel::new())),
25            feature_extractor: Arc::new(CompilationFeatureExtractor::new()),
26        }
27    }
28
29    pub fn optimize(&self, ir: &QuantumIR, target: TargetPlatform) -> QuantRS2Result<QuantumIR> {
30        let features = self.feature_extractor.extract_features(ir, target)?;
31
32        // Compute strategy, then drop the lock before applying transforms.
33        let strategy = {
34            let model = self
35                .model
36                .lock()
37                .map_err(|e| QuantRS2Error::RuntimeError(format!("Model lock poisoned: {e}")))?;
38            model.predict_strategy(&features)?
39        };
40
41        // Apply ML-guided optimizations using the predicted strategy.
42        let optimized = Self::apply_ml_optimizations(ir, &strategy)?;
43
44        Ok(optimized)
45    }
46
47    /// Apply ML-guided optimization transforms in sequence.
48    ///
49    /// When the strategy carries no explicit transformations (e.g., the model
50    /// is a placeholder and returns an empty list), all four transforms are
51    /// applied in a canonical order so this path is never a no-op.
52    fn apply_ml_optimizations(
53        ir: &QuantumIR,
54        strategy: &MLOptimizationStrategy,
55    ) -> QuantRS2Result<QuantumIR> {
56        if strategy.transformations.is_empty() {
57            // Fallback: apply all transforms in canonical order.
58            let ir = Self::apply_rotation_merging_transform(ir)?;
59            let ir = Self::apply_gate_fusion_transform(&ir)?;
60            let ir = Self::apply_commutation_transform(&ir)?;
61            let ir = Self::apply_decomposition_transform(&ir)?;
62            return Ok(ir);
63        }
64
65        let mut current = ir.clone();
66        for transform in &strategy.transformations {
67            current = match transform.transform_type {
68                TransformationType::GateFusion => Self::apply_gate_fusion_transform(&current)?,
69                TransformationType::RotationMerging => {
70                    Self::apply_rotation_merging_transform(&current)?
71                }
72                TransformationType::Commutation => Self::apply_commutation_transform(&current)?,
73                TransformationType::Decomposition => Self::apply_decomposition_transform(&current)?,
74            };
75        }
76        Ok(current)
77    }
78
79    // -----------------------------------------------------------------------
80    // Private helpers: gate classification
81    // -----------------------------------------------------------------------
82
83    /// Returns true when the gate acts on exactly one qubit (single-qubit gates).
84    fn is_single_qubit_gate(gate: &IRGate) -> bool {
85        matches!(
86            gate,
87            IRGate::H
88                | IRGate::X
89                | IRGate::Y
90                | IRGate::Z
91                | IRGate::S
92                | IRGate::T
93                | IRGate::RX(_)
94                | IRGate::RY(_)
95                | IRGate::RZ(_)
96                | IRGate::U1(_)
97                | IRGate::U2(_, _)
98                | IRGate::U3(_, _, _)
99        )
100    }
101
102    /// Extract the qubit set for an operation (all qubits involved, including controls).
103    fn op_qubits(op: &IROperation) -> Vec<usize> {
104        let mut q = op.qubits.clone();
105        q.extend_from_slice(&op.controls);
106        q.sort_unstable();
107        q.dedup();
108        q
109    }
110
111    /// Returns true when the two operations act on entirely disjoint qubit sets.
112    fn qubits_are_disjoint(a: &IROperation, b: &IROperation) -> bool {
113        let qa = Self::op_qubits(a);
114        let qb = Self::op_qubits(b);
115        !qa.iter().any(|q| qb.contains(q))
116    }
117
118    // -----------------------------------------------------------------------
119    // Transform: RotationMerging
120    // -----------------------------------------------------------------------
121
122    /// Combine consecutive same-type rotation gates on the same qubit by
123    /// summing their angles (mod 2π).  If the resulting angle is < ε the
124    /// gate pair is dropped entirely.
125    fn apply_rotation_merging_transform(ir: &QuantumIR) -> QuantRS2Result<QuantumIR> {
126        const EPSILON: f64 = 1e-9;
127        let ops = &ir.operations;
128        let mut result: Vec<IROperation> = Vec::with_capacity(ops.len());
129
130        for op in ops {
131            let merged = if let Some(last) = result.last_mut() {
132                // Only merge if both are single-qubit gates on the same single qubit.
133                if last.qubits.len() == 1 && op.qubits.len() == 1 && last.qubits[0] == op.qubits[0]
134                {
135                    Self::try_merge_rotations(&last.operation_type, &op.operation_type)
136                } else {
137                    None
138                }
139            } else {
140                None
141            };
142
143            match merged {
144                Some(Some(merged_type)) => {
145                    // Replace the last operation with the merged gate.
146                    let last = result.last_mut().ok_or_else(|| {
147                        QuantRS2Error::RuntimeError("Internal merge error".to_string())
148                    })?;
149                    last.operation_type = merged_type;
150                }
151                Some(None) => {
152                    // Angle sums to ~0 — remove the last gate entirely.
153                    result.pop();
154                }
155                None => {
156                    result.push(op.clone());
157                }
158            }
159        }
160
161        let mut out = ir.clone();
162        out.operations = result;
163        Ok(out)
164    }
165
166    /// Try to merge two consecutive `IROperationType` values into one rotation.
167    ///
168    /// Returns:
169    /// - `Some(Some(merged))` — successfully merged.
170    /// - `Some(None)` — angle cancelled to zero; remove both.
171    /// - `None` — not mergeable.
172    fn try_merge_rotations(
173        a: &IROperationType,
174        b: &IROperationType,
175    ) -> Option<Option<IROperationType>> {
176        const EPSILON: f64 = 1e-9;
177        let two_pi = 2.0 * PI;
178
179        match (a, b) {
180            (IROperationType::Gate(IRGate::RX(t1)), IROperationType::Gate(IRGate::RX(t2))) => {
181                let sum = (t1 + t2).rem_euclid(two_pi);
182                if sum.abs() < EPSILON || (sum - two_pi).abs() < EPSILON {
183                    Some(None)
184                } else {
185                    Some(Some(IROperationType::Gate(IRGate::RX(sum))))
186                }
187            }
188            (IROperationType::Gate(IRGate::RY(t1)), IROperationType::Gate(IRGate::RY(t2))) => {
189                let sum = (t1 + t2).rem_euclid(two_pi);
190                if sum.abs() < EPSILON || (sum - two_pi).abs() < EPSILON {
191                    Some(None)
192                } else {
193                    Some(Some(IROperationType::Gate(IRGate::RY(sum))))
194                }
195            }
196            (IROperationType::Gate(IRGate::RZ(t1)), IROperationType::Gate(IRGate::RZ(t2))) => {
197                let sum = (t1 + t2).rem_euclid(two_pi);
198                if sum.abs() < EPSILON || (sum - two_pi).abs() < EPSILON {
199                    Some(None)
200                } else {
201                    Some(Some(IROperationType::Gate(IRGate::RZ(sum))))
202                }
203            }
204            (IROperationType::Gate(IRGate::U1(t1)), IROperationType::Gate(IRGate::U1(t2))) => {
205                let sum = (t1 + t2).rem_euclid(two_pi);
206                if sum.abs() < EPSILON || (sum - two_pi).abs() < EPSILON {
207                    Some(None)
208                } else {
209                    Some(Some(IROperationType::Gate(IRGate::U1(sum))))
210                }
211            }
212            _ => None,
213        }
214    }
215
216    // -----------------------------------------------------------------------
217    // Transform: GateFusion
218    // -----------------------------------------------------------------------
219
220    /// Fuse consecutive single-qubit gates on the same qubit where possible.
221    ///
222    /// This is a superset of `RotationMerging`: same-type rotations are merged
223    /// by angle addition; other pairs are left as-is (no arbitrary matrix
224    /// multiply path exists without a linear-algebra dependency).
225    fn apply_gate_fusion_transform(ir: &QuantumIR) -> QuantRS2Result<QuantumIR> {
226        // For same-type rotation gates delegation is sufficient.
227        // The rotation merging pass already handles the common case.
228        // Here we run it again and additionally handle X–X, Y–Y, Z–Z, H–H
229        // (each pair is the identity and can be dropped).
230        const EPSILON: f64 = 1e-9;
231        let ops = &ir.operations;
232        let mut result: Vec<IROperation> = Vec::with_capacity(ops.len());
233
234        for op in ops {
235            let action = if let Some(last) = result.last() {
236                if last.qubits.len() == 1 && op.qubits.len() == 1 && last.qubits[0] == op.qubits[0]
237                {
238                    // Try rotation merge first.
239                    let rotation_merge =
240                        Self::try_merge_rotations(&last.operation_type, &op.operation_type);
241                    if rotation_merge.is_some() {
242                        rotation_merge.map(|inner| ("rotation", inner))
243                    } else {
244                        // Check self-inverse pairs: gate ∘ gate = I.
245                        Self::try_fuse_self_inverse(&last.operation_type, &op.operation_type)
246                            .map(|_| ("cancel", None))
247                    }
248                } else {
249                    None
250                }
251            } else {
252                None
253            };
254
255            match action {
256                Some(("rotation", Some(merged_type))) => {
257                    let last = result.last_mut().ok_or_else(|| {
258                        QuantRS2Error::RuntimeError("Internal fusion error".to_string())
259                    })?;
260                    last.operation_type = merged_type;
261                }
262                Some((_, None)) => {
263                    // Both cancelled — remove the last gate.
264                    result.pop();
265                }
266                _ => {
267                    result.push(op.clone());
268                }
269            }
270        }
271
272        let mut out = ir.clone();
273        out.operations = result;
274        Ok(out)
275    }
276
277    /// Returns `Some(())` when `a ∘ b = I` (self-inverse pairs).
278    fn try_fuse_self_inverse(a: &IROperationType, b: &IROperationType) -> Option<()> {
279        match (a, b) {
280            (IROperationType::Gate(IRGate::H), IROperationType::Gate(IRGate::H))
281            | (IROperationType::Gate(IRGate::X), IROperationType::Gate(IRGate::X))
282            | (IROperationType::Gate(IRGate::Y), IROperationType::Gate(IRGate::Y))
283            | (IROperationType::Gate(IRGate::Z), IROperationType::Gate(IRGate::Z))
284            | (IROperationType::Gate(IRGate::CNOT), IROperationType::Gate(IRGate::CNOT))
285            | (IROperationType::Gate(IRGate::CZ), IROperationType::Gate(IRGate::CZ)) => Some(()),
286            _ => None,
287        }
288    }
289
290    // -----------------------------------------------------------------------
291    // Transform: Commutation
292    // -----------------------------------------------------------------------
293
294    /// Reorder gates where safe to enable downstream fusion passes.
295    ///
296    /// Single forward pass: for each gate at position i, if it commutes with
297    /// the gate immediately before it (disjoint qubit sets) AND swapping would
298    /// place it adjacent to an earlier gate of the same type on the same qubit,
299    /// swap the pair.  This is deliberately conservative and O(n).
300    fn apply_commutation_transform(ir: &QuantumIR) -> QuantRS2Result<QuantumIR> {
301        let mut ops = ir.operations.clone();
302        let n = ops.len();
303
304        let mut i = 1;
305        while i < n {
306            let commutes = Self::qubits_are_disjoint(&ops[i - 1], &ops[i]);
307            if commutes {
308                // Check if swapping places ops[i] adjacent to a same-type
309                // same-qubit gate further back.
310                let enables_fusion = i >= 2
311                    && ops[i].qubits == ops[i - 2].qubits
312                    && std::mem::discriminant(&ops[i].operation_type)
313                        == std::mem::discriminant(&ops[i - 2].operation_type);
314                if enables_fusion {
315                    ops.swap(i - 1, i);
316                }
317            }
318            i += 1;
319        }
320
321        let mut out = ir.clone();
322        out.operations = ops;
323        Ok(out)
324    }
325
326    // -----------------------------------------------------------------------
327    // Transform: Decomposition
328    // -----------------------------------------------------------------------
329
330    /// Rewrite compound gates into hardware-primitive sequences.
331    ///
332    /// Supported decompositions:
333    /// - `Toffoli` (CCX, 3-qubit) → 15-gate sequence using H, CNOT, T, U1(−π/4).
334    /// - `SWAP` → three CNOT gates.
335    /// - `Fredkin` (CSWAP) → CNOT + Toffoli + CNOT (further decomposed inline).
336    ///
337    /// All other gates pass through unchanged.
338    fn apply_decomposition_transform(ir: &QuantumIR) -> QuantRS2Result<QuantumIR> {
339        let mut out_ops: Vec<IROperation> = Vec::new();
340
341        for op in &ir.operations {
342            match &op.operation_type {
343                IROperationType::Gate(IRGate::Toffoli) if op.qubits.len() >= 3 => {
344                    let (c1, c2, t) = (op.qubits[0], op.qubits[1], op.qubits[2]);
345                    out_ops.extend(Self::decompose_toffoli(c1, c2, t));
346                }
347                IROperationType::Gate(IRGate::SWAP) if op.qubits.len() >= 2 => {
348                    let (a, b) = (op.qubits[0], op.qubits[1]);
349                    out_ops.extend(Self::decompose_swap(a, b));
350                }
351                IROperationType::Gate(IRGate::Fredkin) if op.qubits.len() >= 3 => {
352                    let (ctrl, a, b) = (op.qubits[0], op.qubits[1], op.qubits[2]);
353                    out_ops.extend(Self::decompose_fredkin(ctrl, a, b));
354                }
355                _ => {
356                    out_ops.push(op.clone());
357                }
358            }
359        }
360
361        let mut result = ir.clone();
362        result.operations = out_ops;
363        Ok(result)
364    }
365
366    /// Build a simple single-qubit `IROperation` for the given gate.
367    fn single_qubit_op(gate: IRGate, qubit: usize) -> IROperation {
368        IROperation {
369            operation_type: IROperationType::Gate(gate),
370            qubits: vec![qubit],
371            controls: vec![],
372            parameters: vec![],
373        }
374    }
375
376    /// Build a two-qubit `IROperation` for the given gate.
377    fn two_qubit_op(gate: IRGate, q0: usize, q1: usize) -> IROperation {
378        IROperation {
379            operation_type: IROperationType::Gate(gate),
380            qubits: vec![q0, q1],
381            controls: vec![],
382            parameters: vec![],
383        }
384    }
385
386    /// Toffoli (CCX) → standard 15-gate decomposition.
387    ///
388    /// `Tdg` is not a named variant; we represent T† as `U1(−π/4)`.
389    /// Layout: qubits = [c1, c2, t]
390    fn decompose_toffoli(c1: usize, c2: usize, t: usize) -> Vec<IROperation> {
391        let tdg = |q| Self::single_qubit_op(IRGate::U1(-PI / 4.0), q);
392        let tgate = |q| Self::single_qubit_op(IRGate::T, q);
393        let hgate = |q| Self::single_qubit_op(IRGate::H, q);
394        let cnot = |ctrl, tgt| Self::two_qubit_op(IRGate::CNOT, ctrl, tgt);
395
396        vec![
397            hgate(t),
398            cnot(c2, t),
399            tdg(t),
400            cnot(c1, t),
401            tgate(t),
402            cnot(c2, t),
403            tdg(t),
404            cnot(c1, t),
405            tgate(c2),
406            tgate(t),
407            hgate(t),
408            cnot(c1, c2),
409            tgate(c1),
410            tdg(c2),
411            cnot(c1, c2),
412        ]
413    }
414
415    /// SWAP → three CNOT gates.
416    fn decompose_swap(a: usize, b: usize) -> Vec<IROperation> {
417        vec![
418            Self::two_qubit_op(IRGate::CNOT, a, b),
419            Self::two_qubit_op(IRGate::CNOT, b, a),
420            Self::two_qubit_op(IRGate::CNOT, a, b),
421        ]
422    }
423
424    /// Fredkin (CSWAP, ctrl a b) → CNOT(b,a), Toffoli(ctrl,a,b), CNOT(b,a).
425    fn decompose_fredkin(ctrl: usize, a: usize, b: usize) -> Vec<IROperation> {
426        let mut ops = vec![Self::two_qubit_op(IRGate::CNOT, b, a)];
427        ops.extend(Self::decompose_toffoli(ctrl, a, b));
428        ops.push(Self::two_qubit_op(IRGate::CNOT, b, a));
429        ops
430    }
431}
432
433/// Compilation monitor
434pub struct CompilationMonitor {
435    config: EnhancedCrossCompilationConfig,
436    metrics: Arc<Mutex<CompilationMetrics>>,
437}
438
439impl CompilationMonitor {
440    pub fn new(config: EnhancedCrossCompilationConfig) -> Self {
441        Self {
442            config,
443            metrics: Arc::new(Mutex::new(CompilationMetrics::new())),
444        }
445    }
446
447    pub fn update_optimization_progress(&self, ir: &QuantumIR) -> QuantRS2Result<()> {
448        let anomaly = {
449            let mut metrics = self
450                .metrics
451                .lock()
452                .map_err(|e| QuantRS2Error::RuntimeError(format!("Metrics lock poisoned: {e}")))?;
453            metrics.update(ir)?;
454            metrics.detect_anomaly()
455        }; // Early drop the lock guard
456
457        // Check for anomalies
458        if anomaly {
459            // Handle anomaly
460        }
461
462        Ok(())
463    }
464}
465
466/// Compilation validator
467pub struct CompilationValidator {
468    config: EnhancedCrossCompilationConfig,
469}
470
471impl CompilationValidator {
472    pub const fn new(config: EnhancedCrossCompilationConfig) -> Self {
473        Self { config }
474    }
475
476    pub fn validate_compilation(
477        &self,
478        source: &SourceCircuit,
479        target_code: &TargetCode,
480        platform: TargetPlatform,
481    ) -> QuantRS2Result<super::types::ValidationResult> {
482        let mut result = super::types::ValidationResult::new();
483
484        // Semantic validation
485        if self.config.base_config.preserve_semantics {
486            let semantic_valid = self.validate_semantics(source, target_code)?;
487            result.semantic_validation = Some(semantic_valid);
488        }
489
490        // Resource validation
491        let resource_valid = self.validate_resources(target_code, platform)?;
492        result.resource_validation = Some(resource_valid);
493
494        // Fidelity validation
495        let fidelity = self.estimate_fidelity(source, target_code)?;
496        result.fidelity_estimate = Some(fidelity);
497
498        result.is_valid = result.semantic_validation.unwrap_or(true)
499            && result.resource_validation.unwrap_or(true)
500            && fidelity >= self.config.base_config.validation_threshold;
501
502        Ok(result)
503    }
504
505    pub const fn validate_semantics(
506        &self,
507        _source: &SourceCircuit,
508        _target: &TargetCode,
509    ) -> QuantRS2Result<bool> {
510        // Semantic validation logic
511        Ok(true)
512    }
513
514    pub const fn validate_resources(
515        &self,
516        _target: &TargetCode,
517        _platform: TargetPlatform,
518    ) -> QuantRS2Result<bool> {
519        // Resource validation logic
520        Ok(true)
521    }
522
523    pub const fn estimate_fidelity(
524        &self,
525        _source: &SourceCircuit,
526        _target: &TargetCode,
527    ) -> QuantRS2Result<f64> {
528        // Fidelity estimation logic
529        Ok(0.99)
530    }
531}
532
533/// ML optimization strategy
534pub struct MLOptimizationStrategy {
535    pub transformations: Vec<IRTransformation>,
536    pub confidence: f64,
537}
538
539/// IR transformation
540pub struct IRTransformation {
541    pub transform_type: TransformationType,
542    pub parameters: HashMap<String, f64>,
543}
544
545/// Transformation type
546pub enum TransformationType {
547    GateFusion,
548    RotationMerging,
549    Commutation,
550    Decomposition,
551}
552
553/// Compilation model
554pub struct CompilationModel {
555    // ML model implementation
556}
557
558impl CompilationModel {
559    pub const fn new() -> Self {
560        Self {}
561    }
562
563    pub const fn predict_strategy(
564        &self,
565        _features: &CompilationFeatures,
566    ) -> QuantRS2Result<MLOptimizationStrategy> {
567        // Placeholder implementation
568        Ok(MLOptimizationStrategy {
569            transformations: vec![],
570            confidence: 0.9,
571        })
572    }
573}
574
575impl Default for CompilationModel {
576    fn default() -> Self {
577        Self::new()
578    }
579}
580
581/// Compilation feature extractor
582pub struct CompilationFeatureExtractor {
583    // Feature extraction logic
584}
585
586impl CompilationFeatureExtractor {
587    pub const fn new() -> Self {
588        Self {}
589    }
590
591    pub const fn extract_features(
592        &self,
593        _ir: &QuantumIR,
594        _target: TargetPlatform,
595    ) -> QuantRS2Result<CompilationFeatures> {
596        Ok(CompilationFeatures {
597            circuit_features: vec![],
598            target_features: vec![],
599            complexity_features: vec![],
600        })
601    }
602}
603
604impl Default for CompilationFeatureExtractor {
605    fn default() -> Self {
606        Self::new()
607    }
608}
609
610/// Compilation features
611pub struct CompilationFeatures {
612    pub circuit_features: Vec<f64>,
613    pub target_features: Vec<f64>,
614    pub complexity_features: Vec<f64>,
615}
616
617/// Compilation metrics
618pub struct CompilationMetrics {
619    pub gate_count: usize,
620    pub circuit_depth: usize,
621    pub optimization_count: usize,
622}
623
624impl CompilationMetrics {
625    pub const fn new() -> Self {
626        Self {
627            gate_count: 0,
628            circuit_depth: 0,
629            optimization_count: 0,
630        }
631    }
632
633    pub fn update(&mut self, ir: &QuantumIR) -> QuantRS2Result<()> {
634        self.gate_count = ir.operations.len();
635        // Calculate depth and other metrics
636        Ok(())
637    }
638
639    pub const fn detect_anomaly(&self) -> bool {
640        // Simple anomaly detection
641        false
642    }
643}
644
645impl Default for CompilationMetrics {
646    fn default() -> Self {
647        Self::new()
648    }
649}
650
651/// Target specification
652pub struct TargetSpecification {
653    pub native_gates: Vec<IRGate>,
654    pub connectivity: Vec<(usize, usize)>,
655    pub error_rates: HashMap<String, f64>,
656}
657
658/// Compilation cache
659pub struct CompilationCache {
660    pub cache: HashMap<(String, TargetPlatform), super::types::CrossCompilationResult>,
661}
662
663impl CompilationCache {
664    pub fn new() -> Self {
665        Self {
666            cache: HashMap::new(),
667        }
668    }
669}
670
671impl Default for CompilationCache {
672    fn default() -> Self {
673        Self::new()
674    }
675}
676
677#[cfg(test)]
678mod tests {
679    use super::*;
680    use std::collections::HashMap;
681
682    // Build a minimal QuantumIR with the given operations.
683    fn build_ir(num_qubits: usize, ops: Vec<IROperation>) -> QuantumIR {
684        QuantumIR {
685            num_qubits,
686            num_classical_bits: 0,
687            operations: ops,
688            classical_operations: vec![],
689            metadata: HashMap::new(),
690        }
691    }
692
693    // Build a simple single-qubit gate operation.
694    fn single_gate(gate: IRGate, qubit: usize) -> IROperation {
695        IROperation {
696            operation_type: IROperationType::Gate(gate),
697            qubits: vec![qubit],
698            controls: vec![],
699            parameters: vec![],
700        }
701    }
702
703    // Build a two-qubit gate operation.
704    fn two_qubit_gate(gate: IRGate, q0: usize, q1: usize) -> IROperation {
705        IROperation {
706            operation_type: IROperationType::Gate(gate),
707            qubits: vec![q0, q1],
708            controls: vec![],
709            parameters: vec![],
710        }
711    }
712
713    // Build a three-qubit gate operation.
714    fn three_qubit_gate(gate: IRGate, q0: usize, q1: usize, q2: usize) -> IROperation {
715        IROperation {
716            operation_type: IROperationType::Gate(gate),
717            qubits: vec![q0, q1, q2],
718            controls: vec![],
719            parameters: vec![],
720        }
721    }
722
723    // -----------------------------------------------------------------------
724    // RotationMerging tests
725    // -----------------------------------------------------------------------
726
727    #[test]
728    fn test_rotation_merging_combines_rx_angles() {
729        let ir = build_ir(
730            1,
731            vec![
732                single_gate(IRGate::RX(0.5), 0),
733                single_gate(IRGate::RX(0.3), 0),
734            ],
735        );
736        let result = MLCompilationOptimizer::apply_rotation_merging_transform(&ir).unwrap();
737        assert_eq!(
738            result.operations.len(),
739            1,
740            "two RX gates should merge to one"
741        );
742        match &result.operations[0].operation_type {
743            IROperationType::Gate(IRGate::RX(angle)) => {
744                let expected = (0.5f64 + 0.3).rem_euclid(2.0 * std::f64::consts::PI);
745                assert!(
746                    (angle - expected).abs() < 1e-9,
747                    "merged angle should be 0.8, got {angle}"
748                );
749            }
750            other => panic!("expected RX gate, got {other:?}"),
751        }
752    }
753
754    #[test]
755    fn test_rotation_merging_removes_cancelling_rx() {
756        let angle = std::f64::consts::PI;
757        let ir = build_ir(
758            1,
759            vec![
760                single_gate(IRGate::RX(angle), 0),
761                single_gate(IRGate::RX(-angle), 0),
762            ],
763        );
764        let result = MLCompilationOptimizer::apply_rotation_merging_transform(&ir).unwrap();
765        assert_eq!(
766            result.operations.len(),
767            0,
768            "RX(π) + RX(-π) should cancel to zero gates"
769        );
770    }
771
772    #[test]
773    fn test_rotation_merging_different_qubits_unchanged() {
774        let ir = build_ir(
775            2,
776            vec![
777                single_gate(IRGate::RX(0.5), 0),
778                single_gate(IRGate::RX(0.5), 1), // different qubit — no merge
779            ],
780        );
781        let result = MLCompilationOptimizer::apply_rotation_merging_transform(&ir).unwrap();
782        assert_eq!(
783            result.operations.len(),
784            2,
785            "gates on different qubits must not merge"
786        );
787    }
788
789    #[test]
790    fn test_rotation_merging_different_types_unchanged() {
791        let ir = build_ir(
792            1,
793            vec![
794                single_gate(IRGate::RX(0.5), 0),
795                single_gate(IRGate::RY(0.5), 0), // different type — no merge
796            ],
797        );
798        let result = MLCompilationOptimizer::apply_rotation_merging_transform(&ir).unwrap();
799        assert_eq!(
800            result.operations.len(),
801            2,
802            "RX + RY on same qubit must not merge"
803        );
804    }
805
806    // -----------------------------------------------------------------------
807    // GateFusion tests
808    // -----------------------------------------------------------------------
809
810    #[test]
811    fn test_gate_fusion_reduces_same_type_rotations() {
812        let ir = build_ir(
813            1,
814            vec![
815                single_gate(IRGate::RZ(1.0), 0),
816                single_gate(IRGate::RZ(0.5), 0),
817            ],
818        );
819        let result = MLCompilationOptimizer::apply_gate_fusion_transform(&ir).unwrap();
820        assert_eq!(
821            result.operations.len(),
822            1,
823            "consecutive RZ on same qubit should fuse to 1 gate"
824        );
825    }
826
827    #[test]
828    fn test_gate_fusion_cancels_h_h() {
829        // H ∘ H = I
830        let ir = build_ir(
831            1,
832            vec![single_gate(IRGate::H, 0), single_gate(IRGate::H, 0)],
833        );
834        let result = MLCompilationOptimizer::apply_gate_fusion_transform(&ir).unwrap();
835        assert_eq!(
836            result.operations.len(),
837            0,
838            "H followed by H should cancel to zero gates"
839        );
840    }
841
842    #[test]
843    fn test_gate_fusion_cancels_x_x() {
844        let ir = build_ir(
845            1,
846            vec![single_gate(IRGate::X, 0), single_gate(IRGate::X, 0)],
847        );
848        let result = MLCompilationOptimizer::apply_gate_fusion_transform(&ir).unwrap();
849        assert_eq!(result.operations.len(), 0, "X ∘ X should cancel");
850    }
851
852    // -----------------------------------------------------------------------
853    // Commutation tests
854    // -----------------------------------------------------------------------
855
856    #[test]
857    fn test_commutation_reorders_disjoint_qubits() {
858        // Circuit: RX(q=0), RX(q=1), RX(q=0)
859        // Gate at i=1 (q=1) commutes with i=0 (q=0) — disjoint.
860        // After swap, i=0 is RX(q=1) and i=1 is RX(q=0), which is NOT i-2 check.
861        // After second swap opportunity at i=2, ops[2] (q=0) vs ops[1] (q=0):
862        // they don't commute (same qubit).
863        // The test verifies that at minimum the function completes without error
864        // and returns valid gate count.
865        let ir = build_ir(
866            2,
867            vec![
868                single_gate(IRGate::RX(0.5), 0),
869                single_gate(IRGate::RX(0.5), 1),
870                single_gate(IRGate::RX(0.3), 0),
871            ],
872        );
873        let result = MLCompilationOptimizer::apply_commutation_transform(&ir).unwrap();
874        // Gate count is unchanged by commutation.
875        assert_eq!(
876            result.operations.len(),
877            3,
878            "commutation preserves gate count"
879        );
880    }
881
882    #[test]
883    fn test_commutation_enables_downstream_fusion() {
884        // Circuit: RX(q=0), RX(q=1), RX(q=0)
885        // After commutation the RX(q=0) at position 2 should be moved next to
886        // RX(q=0) at position 0 (since RX(q=1) commutes with both).
887        let ir = build_ir(
888            2,
889            vec![
890                single_gate(IRGate::RX(0.5), 0),
891                single_gate(IRGate::RX(0.5), 1), // commutes with neighbors on q=0
892                single_gate(IRGate::RX(0.3), 0),
893            ],
894        );
895        let commuted = MLCompilationOptimizer::apply_commutation_transform(&ir).unwrap();
896        // After commutation + fusion we should get 2 ops (one merged RX on q=0,
897        // one RX on q=1) instead of 3.
898        let fused = MLCompilationOptimizer::apply_rotation_merging_transform(&commuted).unwrap();
899        assert_eq!(
900            fused.operations.len(),
901            2,
902            "commutation + rotation-merge should collapse two RX(q=0) into one"
903        );
904    }
905
906    // -----------------------------------------------------------------------
907    // Decomposition tests
908    // -----------------------------------------------------------------------
909
910    #[test]
911    fn test_decomposition_toffoli_produces_15_gates() {
912        let ir = build_ir(3, vec![three_qubit_gate(IRGate::Toffoli, 0, 1, 2)]);
913        let result = MLCompilationOptimizer::apply_decomposition_transform(&ir).unwrap();
914        assert_eq!(
915            result.operations.len(),
916            15,
917            "Toffoli should decompose into exactly 15 primitive gates"
918        );
919    }
920
921    #[test]
922    fn test_decomposition_swap_produces_3_cnots() {
923        let ir = build_ir(2, vec![two_qubit_gate(IRGate::SWAP, 0, 1)]);
924        let result = MLCompilationOptimizer::apply_decomposition_transform(&ir).unwrap();
925        assert_eq!(
926            result.operations.len(),
927            3,
928            "SWAP should decompose into exactly 3 CNOT gates"
929        );
930        for op in &result.operations {
931            assert!(
932                matches!(&op.operation_type, IROperationType::Gate(IRGate::CNOT)),
933                "each SWAP decomposition gate should be a CNOT, got {:?}",
934                op.operation_type
935            );
936        }
937    }
938
939    #[test]
940    fn test_decomposition_non_compound_passes_through() {
941        let ir = build_ir(
942            1,
943            vec![single_gate(IRGate::H, 0), single_gate(IRGate::RX(1.0), 0)],
944        );
945        let result = MLCompilationOptimizer::apply_decomposition_transform(&ir).unwrap();
946        assert_eq!(
947            result.operations.len(),
948            2,
949            "non-compound gates should pass through unchanged"
950        );
951    }
952
953    // -----------------------------------------------------------------------
954    // End-to-end apply_ml_optimizations test
955    // -----------------------------------------------------------------------
956
957    #[test]
958    fn test_apply_ml_optimizations_fallback_path() {
959        // Verify the fallback (empty strategy) path executes without error.
960        let strategy = MLOptimizationStrategy {
961            transformations: vec![],
962            confidence: 0.9,
963        };
964        let ir = build_ir(
965            1,
966            vec![
967                single_gate(IRGate::RX(0.5), 0),
968                single_gate(IRGate::RX(0.5), 0),
969            ],
970        );
971        let result = MLCompilationOptimizer::apply_ml_optimizations(&ir, &strategy).unwrap();
972        // After rotation merging both RX gates should collapse to one.
973        assert_eq!(
974            result.operations.len(),
975            1,
976            "fallback path should apply rotation merging and fuse the two RX gates"
977        );
978    }
979}