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    /// Structural semantic-equivalence check between the original source
506    /// text and the generated target code.
507    ///
508    /// `SourceCircuit` stores the original program as an opaque,
509    /// framework-specific source string (Qiskit/Cirq/PennyLane/OpenQASM
510    /// text, etc.) rather than a parsed representation, so a true
511    /// unitary/statevector equivalence check is not available at this
512    /// layer. Instead this compares the *gate-name histograms* found in
513    /// both texts (a format-agnostic structural heuristic: real quantum
514    /// gate mnemonics like `h`, `cx`, `rz`, ... appear as identifiers in
515    /// essentially every textual quantum programming language/IR dump) and
516    /// accepts the compilation only when the two histograms are similar
517    /// enough (cosine similarity) to plausibly represent the same circuit.
518    pub fn validate_semantics(
519        &self,
520        source: &SourceCircuit,
521        target: &TargetCode,
522    ) -> QuantRS2Result<bool> {
523        let source_gate_counts = extract_gate_token_counts(&source.code);
524        let target_gate_counts = extract_gate_token_counts(&target.code);
525        let similarity = gate_histogram_similarity(&source_gate_counts, &target_gate_counts);
526
527        Ok(similarity >= SEMANTIC_SIMILARITY_THRESHOLD)
528    }
529
530    /// Real resource-capacity check: estimates the number of qubits
531    /// referenced by the generated target code (from bracketed qubit-index
532    /// syntax such as `q[3]`, common to QASM/Quil-style output) and compares
533    /// it against the target platform's known qubit capacity.
534    pub fn validate_resources(
535        &self,
536        target: &TargetCode,
537        platform: TargetPlatform,
538    ) -> QuantRS2Result<bool> {
539        let estimated_qubits = estimate_qubit_count_from_code(&target.code);
540        let platform_capacity = platform_max_qubits(platform);
541
542        Ok(estimated_qubits <= platform_capacity)
543    }
544
545    /// Real fidelity estimate derived from the generated target code: the
546    /// per-gate-type counts extracted from `target.code` are combined with
547    /// typical single-/two-qubit gate fidelities published for the target
548    /// hardware platform (the same style of domain-derived error-rate data
549    /// used by [`crate::noise_models::NoiseModel`]) into a product-model
550    /// circuit fidelity, then scaled by the source/target structural
551    /// similarity used in [`Self::validate_semantics`] so that a compilation
552    /// which diverges structurally from its source is never scored as
553    /// perfectly faithful.
554    pub fn estimate_fidelity(
555        &self,
556        source: &SourceCircuit,
557        target: &TargetCode,
558    ) -> QuantRS2Result<f64> {
559        let target_gate_counts = extract_gate_token_counts(&target.code);
560        let (single_qubit_fidelity, two_qubit_fidelity) = platform_gate_fidelities(target.platform);
561
562        let single_qubit_gate_count: i32 = SINGLE_QUBIT_GATE_TOKENS
563            .iter()
564            .map(|name| *target_gate_counts.get(*name).unwrap_or(&0) as i32)
565            .sum();
566        let two_qubit_gate_count: i32 = TWO_QUBIT_GATE_TOKENS
567            .iter()
568            .map(|name| *target_gate_counts.get(*name).unwrap_or(&0) as i32)
569            .sum();
570
571        let gate_composition_fidelity = single_qubit_fidelity.powi(single_qubit_gate_count)
572            * two_qubit_fidelity.powi(two_qubit_gate_count);
573
574        let source_gate_counts = extract_gate_token_counts(&source.code);
575        let structural_similarity =
576            gate_histogram_similarity(&source_gate_counts, &target_gate_counts);
577
578        Ok((gate_composition_fidelity * structural_similarity).clamp(0.0, 1.0))
579    }
580}
581
582/// Canonical, format-agnostic quantum gate name tokens recognized when
583/// scanning generated/source code text for structural comparison.
584const SINGLE_QUBIT_GATE_TOKENS: [&str; 13] = [
585    "h", "x", "y", "z", "s", "sdg", "t", "tdg", "rx", "ry", "rz", "u1", "u2",
586];
587const TWO_QUBIT_GATE_TOKENS: [&str; 6] = ["cx", "cnot", "cz", "swap", "iswap", "ch"];
588const MULTI_QUBIT_GATE_TOKENS: [&str; 4] = ["ccx", "toffoli", "cswap", "fredkin"];
589
590/// Tokenize `code` on non-alphanumeric boundaries and count occurrences of
591/// recognized gate-name tokens (case-insensitive).
592fn extract_gate_token_counts(code: &str) -> HashMap<String, usize> {
593    let mut counts = HashMap::new();
594    for raw_token in code.split(|c: char| !c.is_ascii_alphanumeric()) {
595        if raw_token.is_empty() {
596            continue;
597        }
598        let token = raw_token.to_ascii_lowercase();
599        let is_known_gate = SINGLE_QUBIT_GATE_TOKENS.contains(&token.as_str())
600            || TWO_QUBIT_GATE_TOKENS.contains(&token.as_str())
601            || MULTI_QUBIT_GATE_TOKENS.contains(&token.as_str());
602        if is_known_gate {
603            *counts.entry(token).or_insert(0_usize) += 1;
604        }
605    }
606    counts
607}
608
609/// Cosine similarity between two gate-name histograms. Two histograms that
610/// are both empty (no recognized gates in either text) are treated as
611/// trivially similar (score `1.0`); one empty and one non-empty are
612/// treated as maximally dissimilar (score `0.0`).
613fn gate_histogram_similarity(a: &HashMap<String, usize>, b: &HashMap<String, usize>) -> f64 {
614    let all_tokens = SINGLE_QUBIT_GATE_TOKENS
615        .iter()
616        .chain(TWO_QUBIT_GATE_TOKENS.iter())
617        .chain(MULTI_QUBIT_GATE_TOKENS.iter());
618
619    let mut dot_product = 0.0_f64;
620    let mut norm_a = 0.0_f64;
621    let mut norm_b = 0.0_f64;
622    for token in all_tokens {
623        let a_count = *a.get(*token).unwrap_or(&0) as f64;
624        let b_count = *b.get(*token).unwrap_or(&0) as f64;
625        dot_product += a_count * b_count;
626        norm_a += a_count * a_count;
627        norm_b += b_count * b_count;
628    }
629
630    if norm_a == 0.0 && norm_b == 0.0 {
631        1.0
632    } else if norm_a == 0.0 || norm_b == 0.0 {
633        0.0
634    } else {
635        dot_product / (norm_a.sqrt() * norm_b.sqrt())
636    }
637}
638
639/// Minimum gate-histogram cosine similarity required to accept a
640/// compilation as semantically consistent with its source.
641const SEMANTIC_SIMILARITY_THRESHOLD: f64 = 0.5;
642
643/// Estimate the number of qubits referenced by generated code text by
644/// scanning for the largest integer found inside bracket/parenthesis
645/// syntax (`q[3]`, `qubit(3)`, ...), a pattern shared by QASM, Quil, and
646/// most textual quantum IR dumps. Returns `0` when no qubit index syntax
647/// is found (e.g. an empty circuit).
648fn estimate_qubit_count_from_code(code: &str) -> usize {
649    let mut max_index: Option<usize> = None;
650    let mut digits = String::new();
651    let mut chars = code.chars().peekable();
652
653    while let Some(c) = chars.next() {
654        if c == '[' || c == '(' {
655            digits.clear();
656            while let Some(&next) = chars.peek() {
657                if next.is_ascii_digit() {
658                    digits.push(next);
659                    chars.next();
660                } else {
661                    break;
662                }
663            }
664            if let Ok(index) = digits.parse::<usize>() {
665                max_index = Some(max_index.map_or(index, |current| current.max(index)));
666            }
667        }
668    }
669
670    max_index.map_or(0, |index| index + 1)
671}
672
673/// Estimate circuit depth via greedy list scheduling: each operation's
674/// layer is one past the deepest layer among the qubits (and controls) it
675/// touches, and each of those qubits is advanced to that layer. The
676/// circuit depth is the maximum layer reached across all qubits.
677fn estimate_circuit_depth(ir: &QuantumIR) -> usize {
678    let mut qubit_layer: HashMap<usize, usize> = HashMap::new();
679    let mut max_layer = 0_usize;
680
681    for op in &ir.operations {
682        let touched_qubits: Vec<usize> = match &op.operation_type {
683            IROperationType::Gate(_) => {
684                let mut qubits = op.qubits.clone();
685                qubits.extend_from_slice(&op.controls);
686                qubits
687            }
688            IROperationType::Measurement(qubits, _)
689            | IROperationType::Reset(qubits)
690            | IROperationType::Barrier(qubits) => qubits.clone(),
691        };
692
693        if touched_qubits.is_empty() {
694            continue;
695        }
696
697        let current_layer = touched_qubits
698            .iter()
699            .map(|q| qubit_layer.get(q).copied().unwrap_or(0))
700            .max()
701            .unwrap_or(0);
702        let new_layer = current_layer + 1;
703
704        for q in &touched_qubits {
705            qubit_layer.insert(*q, new_layer);
706        }
707        max_layer = max_layer.max(new_layer);
708    }
709
710    max_layer
711}
712
713/// Known (approximate, publicly documented) qubit capacity for each
714/// supported target platform.
715const fn platform_max_qubits(platform: TargetPlatform) -> usize {
716    match platform {
717        TargetPlatform::IBMQuantum => 127,
718        TargetPlatform::GoogleSycamore => 70,
719        TargetPlatform::IonQ => 32,
720        TargetPlatform::Rigetti => 80,
721        TargetPlatform::Honeywell => 32,
722        TargetPlatform::AWSBraket => 34,
723        TargetPlatform::AzureQuantum => 40,
724        TargetPlatform::Simulator => 1_000,
725    }
726}
727
728/// Typical (single-qubit, two-qubit) gate fidelities published for each
729/// target platform's native gate set, used as a real (if approximate)
730/// per-gate error model rather than a fixed constant.
731const fn platform_gate_fidelities(platform: TargetPlatform) -> (f64, f64) {
732    match platform {
733        TargetPlatform::IBMQuantum => (0.9999, 0.99),
734        TargetPlatform::GoogleSycamore => (0.9998, 0.995),
735        TargetPlatform::IonQ => (0.9995, 0.998),
736        TargetPlatform::Rigetti => (0.999, 0.98),
737        TargetPlatform::Honeywell => (0.9999, 0.998),
738        TargetPlatform::AWSBraket => (0.999, 0.99),
739        TargetPlatform::AzureQuantum => (0.999, 0.99),
740        TargetPlatform::Simulator => (1.0, 1.0),
741    }
742}
743
744/// ML optimization strategy
745pub struct MLOptimizationStrategy {
746    pub transformations: Vec<IRTransformation>,
747    pub confidence: f64,
748}
749
750/// IR transformation
751pub struct IRTransformation {
752    pub transform_type: TransformationType,
753    pub parameters: HashMap<String, f64>,
754}
755
756/// Transformation type
757pub enum TransformationType {
758    GateFusion,
759    RotationMerging,
760    Commutation,
761    Decomposition,
762}
763
764/// Compilation model
765pub struct CompilationModel {
766    // ML model implementation
767}
768
769impl CompilationModel {
770    pub const fn new() -> Self {
771        Self {}
772    }
773
774    /// Heuristic (rule-based, not trained) strategy predictor: reads the
775    /// real feature vector produced by [`CompilationFeatureExtractor`] and
776    /// selects which IR transformations are actually likely to help, rather
777    /// than returning a fixed empty list. Layout of `circuit_features`
778    /// (see [`CompilationFeatureExtractor::extract_features`]):
779    /// `[num_qubits, total_gates, single_qubit_gates, two_qubit_gates,
780    ///   multi_qubit_gates, rotation_gates, compound_gates]`.
781    pub fn predict_strategy(
782        &self,
783        features: &CompilationFeatures,
784    ) -> QuantRS2Result<MLOptimizationStrategy> {
785        let total_gates = features.circuit_features.get(1).copied().unwrap_or(0.0);
786        let two_qubit_gates = features.circuit_features.get(3).copied().unwrap_or(0.0);
787        let rotation_gates = features.circuit_features.get(5).copied().unwrap_or(0.0);
788        let compound_gates = features.circuit_features.get(6).copied().unwrap_or(0.0);
789
790        let mut transformations = Vec::new();
791        if rotation_gates > 0.0 {
792            transformations.push(IRTransformation {
793                transform_type: TransformationType::RotationMerging,
794                parameters: HashMap::new(),
795            });
796        }
797        if two_qubit_gates > 0.0 || total_gates > 1.0 {
798            transformations.push(IRTransformation {
799                transform_type: TransformationType::GateFusion,
800                parameters: HashMap::new(),
801            });
802            transformations.push(IRTransformation {
803                transform_type: TransformationType::Commutation,
804                parameters: HashMap::new(),
805            });
806        }
807        if compound_gates > 0.0 {
808            transformations.push(IRTransformation {
809                transform_type: TransformationType::Decomposition,
810                parameters: HashMap::new(),
811            });
812        }
813
814        // Confidence reflects how much real evidence backed the decision: a
815        // circuit with no gates gives a low-confidence (uninformed) empty
816        // strategy, while a larger, richer gate set saturates toward (but
817        // never reaches) full confidence.
818        let confidence = if total_gates <= 0.0 {
819            0.5
820        } else {
821            (0.5 + 0.5 * (total_gates / (total_gates + 10.0))).min(0.99)
822        };
823
824        Ok(MLOptimizationStrategy {
825            transformations,
826            confidence,
827        })
828    }
829}
830
831impl Default for CompilationModel {
832    fn default() -> Self {
833        Self::new()
834    }
835}
836
837/// Compilation feature extractor
838pub struct CompilationFeatureExtractor {
839    // Feature extraction logic
840}
841
842impl CompilationFeatureExtractor {
843    pub const fn new() -> Self {
844        Self {}
845    }
846
847    /// Extract a real feature vector from the actual IR and target
848    /// platform, consumed by [`CompilationModel::predict_strategy`].
849    ///
850    /// `circuit_features` layout: `[num_qubits, total_gates,
851    /// single_qubit_gates, two_qubit_gates, multi_qubit_gates,
852    /// rotation_gates, compound_gates]`.
853    /// `target_features` layout: `[platform_qubit_capacity,
854    /// single_qubit_gate_fidelity, two_qubit_gate_fidelity]`.
855    /// `complexity_features` layout: `[gate_density, two_qubit_ratio,
856    /// estimated_depth]`.
857    pub fn extract_features(
858        &self,
859        ir: &QuantumIR,
860        target: TargetPlatform,
861    ) -> QuantRS2Result<CompilationFeatures> {
862        let mut single_qubit_gates = 0.0_f64;
863        let mut two_qubit_gates = 0.0_f64;
864        let mut multi_qubit_gates = 0.0_f64;
865        let mut rotation_gates = 0.0_f64;
866        let mut compound_gates = 0.0_f64;
867        let mut total_gates = 0.0_f64;
868
869        for op in &ir.operations {
870            if let IROperationType::Gate(gate) = &op.operation_type {
871                total_gates += 1.0;
872                match op.qubits.len() {
873                    1 => single_qubit_gates += 1.0,
874                    2 => two_qubit_gates += 1.0,
875                    _ => multi_qubit_gates += 1.0,
876                }
877                if matches!(
878                    gate,
879                    IRGate::RX(_)
880                        | IRGate::RY(_)
881                        | IRGate::RZ(_)
882                        | IRGate::U1(_)
883                        | IRGate::U2(_, _)
884                        | IRGate::U3(_, _, _)
885                ) {
886                    rotation_gates += 1.0;
887                }
888                if matches!(gate, IRGate::Toffoli | IRGate::Fredkin | IRGate::SWAP) {
889                    compound_gates += 1.0;
890                }
891            }
892        }
893
894        let estimated_depth = estimate_circuit_depth(ir) as f64;
895        let (single_qubit_fidelity, two_qubit_fidelity) = platform_gate_fidelities(target);
896        let platform_capacity = platform_max_qubits(target) as f64;
897
898        let gate_density = if ir.num_qubits > 0 {
899            total_gates / ir.num_qubits as f64
900        } else {
901            0.0
902        };
903        let two_qubit_ratio = if total_gates > 0.0 {
904            two_qubit_gates / total_gates
905        } else {
906            0.0
907        };
908
909        Ok(CompilationFeatures {
910            circuit_features: vec![
911                ir.num_qubits as f64,
912                total_gates,
913                single_qubit_gates,
914                two_qubit_gates,
915                multi_qubit_gates,
916                rotation_gates,
917                compound_gates,
918            ],
919            target_features: vec![platform_capacity, single_qubit_fidelity, two_qubit_fidelity],
920            complexity_features: vec![gate_density, two_qubit_ratio, estimated_depth],
921        })
922    }
923}
924
925impl Default for CompilationFeatureExtractor {
926    fn default() -> Self {
927        Self::new()
928    }
929}
930
931/// Compilation features
932pub struct CompilationFeatures {
933    pub circuit_features: Vec<f64>,
934    pub target_features: Vec<f64>,
935    pub complexity_features: Vec<f64>,
936}
937
938/// Compilation metrics
939pub struct CompilationMetrics {
940    pub gate_count: usize,
941    pub circuit_depth: usize,
942    pub optimization_count: usize,
943}
944
945impl CompilationMetrics {
946    pub const fn new() -> Self {
947        Self {
948            gate_count: 0,
949            circuit_depth: 0,
950            optimization_count: 0,
951        }
952    }
953
954    pub fn update(&mut self, ir: &QuantumIR) -> QuantRS2Result<()> {
955        self.gate_count = ir.operations.len();
956        // Calculate depth and other metrics
957        Ok(())
958    }
959
960    pub const fn detect_anomaly(&self) -> bool {
961        // Simple anomaly detection
962        false
963    }
964}
965
966impl Default for CompilationMetrics {
967    fn default() -> Self {
968        Self::new()
969    }
970}
971
972/// Target specification
973pub struct TargetSpecification {
974    pub native_gates: Vec<IRGate>,
975    pub connectivity: Vec<(usize, usize)>,
976    pub error_rates: HashMap<String, f64>,
977}
978
979/// Compilation cache
980pub struct CompilationCache {
981    pub cache: HashMap<(String, TargetPlatform), super::types::CrossCompilationResult>,
982}
983
984impl CompilationCache {
985    pub fn new() -> Self {
986        Self {
987            cache: HashMap::new(),
988        }
989    }
990}
991
992impl Default for CompilationCache {
993    fn default() -> Self {
994        Self::new()
995    }
996}
997
998#[cfg(test)]
999mod tests {
1000    use super::*;
1001    use std::collections::HashMap;
1002
1003    // Build a minimal QuantumIR with the given operations.
1004    fn build_ir(num_qubits: usize, ops: Vec<IROperation>) -> QuantumIR {
1005        QuantumIR {
1006            num_qubits,
1007            num_classical_bits: 0,
1008            operations: ops,
1009            classical_operations: vec![],
1010            metadata: HashMap::new(),
1011        }
1012    }
1013
1014    // Build a simple single-qubit gate operation.
1015    fn single_gate(gate: IRGate, qubit: usize) -> IROperation {
1016        IROperation {
1017            operation_type: IROperationType::Gate(gate),
1018            qubits: vec![qubit],
1019            controls: vec![],
1020            parameters: vec![],
1021        }
1022    }
1023
1024    // Build a two-qubit gate operation.
1025    fn two_qubit_gate(gate: IRGate, q0: usize, q1: usize) -> IROperation {
1026        IROperation {
1027            operation_type: IROperationType::Gate(gate),
1028            qubits: vec![q0, q1],
1029            controls: vec![],
1030            parameters: vec![],
1031        }
1032    }
1033
1034    // Build a three-qubit gate operation.
1035    fn three_qubit_gate(gate: IRGate, q0: usize, q1: usize, q2: usize) -> IROperation {
1036        IROperation {
1037            operation_type: IROperationType::Gate(gate),
1038            qubits: vec![q0, q1, q2],
1039            controls: vec![],
1040            parameters: vec![],
1041        }
1042    }
1043
1044    // -----------------------------------------------------------------------
1045    // RotationMerging tests
1046    // -----------------------------------------------------------------------
1047
1048    #[test]
1049    fn test_rotation_merging_combines_rx_angles() {
1050        let ir = build_ir(
1051            1,
1052            vec![
1053                single_gate(IRGate::RX(0.5), 0),
1054                single_gate(IRGate::RX(0.3), 0),
1055            ],
1056        );
1057        let result = MLCompilationOptimizer::apply_rotation_merging_transform(&ir).unwrap();
1058        assert_eq!(
1059            result.operations.len(),
1060            1,
1061            "two RX gates should merge to one"
1062        );
1063        match &result.operations[0].operation_type {
1064            IROperationType::Gate(IRGate::RX(angle)) => {
1065                let expected = (0.5f64 + 0.3).rem_euclid(2.0 * std::f64::consts::PI);
1066                assert!(
1067                    (angle - expected).abs() < 1e-9,
1068                    "merged angle should be 0.8, got {angle}"
1069                );
1070            }
1071            other => panic!("expected RX gate, got {other:?}"),
1072        }
1073    }
1074
1075    #[test]
1076    fn test_rotation_merging_removes_cancelling_rx() {
1077        let angle = std::f64::consts::PI;
1078        let ir = build_ir(
1079            1,
1080            vec![
1081                single_gate(IRGate::RX(angle), 0),
1082                single_gate(IRGate::RX(-angle), 0),
1083            ],
1084        );
1085        let result = MLCompilationOptimizer::apply_rotation_merging_transform(&ir).unwrap();
1086        assert_eq!(
1087            result.operations.len(),
1088            0,
1089            "RX(π) + RX(-π) should cancel to zero gates"
1090        );
1091    }
1092
1093    #[test]
1094    fn test_rotation_merging_different_qubits_unchanged() {
1095        let ir = build_ir(
1096            2,
1097            vec![
1098                single_gate(IRGate::RX(0.5), 0),
1099                single_gate(IRGate::RX(0.5), 1), // different qubit — no merge
1100            ],
1101        );
1102        let result = MLCompilationOptimizer::apply_rotation_merging_transform(&ir).unwrap();
1103        assert_eq!(
1104            result.operations.len(),
1105            2,
1106            "gates on different qubits must not merge"
1107        );
1108    }
1109
1110    #[test]
1111    fn test_rotation_merging_different_types_unchanged() {
1112        let ir = build_ir(
1113            1,
1114            vec![
1115                single_gate(IRGate::RX(0.5), 0),
1116                single_gate(IRGate::RY(0.5), 0), // different type — no merge
1117            ],
1118        );
1119        let result = MLCompilationOptimizer::apply_rotation_merging_transform(&ir).unwrap();
1120        assert_eq!(
1121            result.operations.len(),
1122            2,
1123            "RX + RY on same qubit must not merge"
1124        );
1125    }
1126
1127    // -----------------------------------------------------------------------
1128    // GateFusion tests
1129    // -----------------------------------------------------------------------
1130
1131    #[test]
1132    fn test_gate_fusion_reduces_same_type_rotations() {
1133        let ir = build_ir(
1134            1,
1135            vec![
1136                single_gate(IRGate::RZ(1.0), 0),
1137                single_gate(IRGate::RZ(0.5), 0),
1138            ],
1139        );
1140        let result = MLCompilationOptimizer::apply_gate_fusion_transform(&ir).unwrap();
1141        assert_eq!(
1142            result.operations.len(),
1143            1,
1144            "consecutive RZ on same qubit should fuse to 1 gate"
1145        );
1146    }
1147
1148    #[test]
1149    fn test_gate_fusion_cancels_h_h() {
1150        // H ∘ H = I
1151        let ir = build_ir(
1152            1,
1153            vec![single_gate(IRGate::H, 0), single_gate(IRGate::H, 0)],
1154        );
1155        let result = MLCompilationOptimizer::apply_gate_fusion_transform(&ir).unwrap();
1156        assert_eq!(
1157            result.operations.len(),
1158            0,
1159            "H followed by H should cancel to zero gates"
1160        );
1161    }
1162
1163    #[test]
1164    fn test_gate_fusion_cancels_x_x() {
1165        let ir = build_ir(
1166            1,
1167            vec![single_gate(IRGate::X, 0), single_gate(IRGate::X, 0)],
1168        );
1169        let result = MLCompilationOptimizer::apply_gate_fusion_transform(&ir).unwrap();
1170        assert_eq!(result.operations.len(), 0, "X ∘ X should cancel");
1171    }
1172
1173    // -----------------------------------------------------------------------
1174    // Commutation tests
1175    // -----------------------------------------------------------------------
1176
1177    #[test]
1178    fn test_commutation_reorders_disjoint_qubits() {
1179        // Circuit: RX(q=0), RX(q=1), RX(q=0)
1180        // Gate at i=1 (q=1) commutes with i=0 (q=0) — disjoint.
1181        // After swap, i=0 is RX(q=1) and i=1 is RX(q=0), which is NOT i-2 check.
1182        // After second swap opportunity at i=2, ops[2] (q=0) vs ops[1] (q=0):
1183        // they don't commute (same qubit).
1184        // The test verifies that at minimum the function completes without error
1185        // and returns valid gate count.
1186        let ir = build_ir(
1187            2,
1188            vec![
1189                single_gate(IRGate::RX(0.5), 0),
1190                single_gate(IRGate::RX(0.5), 1),
1191                single_gate(IRGate::RX(0.3), 0),
1192            ],
1193        );
1194        let result = MLCompilationOptimizer::apply_commutation_transform(&ir).unwrap();
1195        // Gate count is unchanged by commutation.
1196        assert_eq!(
1197            result.operations.len(),
1198            3,
1199            "commutation preserves gate count"
1200        );
1201    }
1202
1203    #[test]
1204    fn test_commutation_enables_downstream_fusion() {
1205        // Circuit: RX(q=0), RX(q=1), RX(q=0)
1206        // After commutation the RX(q=0) at position 2 should be moved next to
1207        // RX(q=0) at position 0 (since RX(q=1) commutes with both).
1208        let ir = build_ir(
1209            2,
1210            vec![
1211                single_gate(IRGate::RX(0.5), 0),
1212                single_gate(IRGate::RX(0.5), 1), // commutes with neighbors on q=0
1213                single_gate(IRGate::RX(0.3), 0),
1214            ],
1215        );
1216        let commuted = MLCompilationOptimizer::apply_commutation_transform(&ir).unwrap();
1217        // After commutation + fusion we should get 2 ops (one merged RX on q=0,
1218        // one RX on q=1) instead of 3.
1219        let fused = MLCompilationOptimizer::apply_rotation_merging_transform(&commuted).unwrap();
1220        assert_eq!(
1221            fused.operations.len(),
1222            2,
1223            "commutation + rotation-merge should collapse two RX(q=0) into one"
1224        );
1225    }
1226
1227    // -----------------------------------------------------------------------
1228    // Decomposition tests
1229    // -----------------------------------------------------------------------
1230
1231    #[test]
1232    fn test_decomposition_toffoli_produces_15_gates() {
1233        let ir = build_ir(3, vec![three_qubit_gate(IRGate::Toffoli, 0, 1, 2)]);
1234        let result = MLCompilationOptimizer::apply_decomposition_transform(&ir).unwrap();
1235        assert_eq!(
1236            result.operations.len(),
1237            15,
1238            "Toffoli should decompose into exactly 15 primitive gates"
1239        );
1240    }
1241
1242    #[test]
1243    fn test_decomposition_swap_produces_3_cnots() {
1244        let ir = build_ir(2, vec![two_qubit_gate(IRGate::SWAP, 0, 1)]);
1245        let result = MLCompilationOptimizer::apply_decomposition_transform(&ir).unwrap();
1246        assert_eq!(
1247            result.operations.len(),
1248            3,
1249            "SWAP should decompose into exactly 3 CNOT gates"
1250        );
1251        for op in &result.operations {
1252            assert!(
1253                matches!(&op.operation_type, IROperationType::Gate(IRGate::CNOT)),
1254                "each SWAP decomposition gate should be a CNOT, got {:?}",
1255                op.operation_type
1256            );
1257        }
1258    }
1259
1260    #[test]
1261    fn test_decomposition_non_compound_passes_through() {
1262        let ir = build_ir(
1263            1,
1264            vec![single_gate(IRGate::H, 0), single_gate(IRGate::RX(1.0), 0)],
1265        );
1266        let result = MLCompilationOptimizer::apply_decomposition_transform(&ir).unwrap();
1267        assert_eq!(
1268            result.operations.len(),
1269            2,
1270            "non-compound gates should pass through unchanged"
1271        );
1272    }
1273
1274    // -----------------------------------------------------------------------
1275    // End-to-end apply_ml_optimizations test
1276    // -----------------------------------------------------------------------
1277
1278    #[test]
1279    fn test_apply_ml_optimizations_fallback_path() {
1280        // Verify the fallback (empty strategy) path executes without error.
1281        let strategy = MLOptimizationStrategy {
1282            transformations: vec![],
1283            confidence: 0.9,
1284        };
1285        let ir = build_ir(
1286            1,
1287            vec![
1288                single_gate(IRGate::RX(0.5), 0),
1289                single_gate(IRGate::RX(0.5), 0),
1290            ],
1291        );
1292        let result = MLCompilationOptimizer::apply_ml_optimizations(&ir, &strategy).unwrap();
1293        // After rotation merging both RX gates should collapse to one.
1294        assert_eq!(
1295            result.operations.len(),
1296            1,
1297            "fallback path should apply rotation merging and fuse the two RX gates"
1298        );
1299    }
1300
1301    // -----------------------------------------------------------------------
1302    // CompilationValidator tests: validate_semantics / validate_resources /
1303    // estimate_fidelity must now depend on their inputs, not return
1304    // hardcoded true/true/0.99 for every circuit.
1305    // -----------------------------------------------------------------------
1306
1307    fn make_source(code: &str) -> SourceCircuit {
1308        SourceCircuit {
1309            framework: crate::scirs2_cross_compilation_enhanced::QuantumFramework::OpenQASM,
1310            code: code.to_string(),
1311            metadata: HashMap::new(),
1312        }
1313    }
1314
1315    fn make_target(code: &str, platform: TargetPlatform) -> TargetCode {
1316        TargetCode {
1317            platform,
1318            code: code.to_string(),
1319            format: crate::scirs2_cross_compilation_enhanced::CodeFormat::QASM,
1320            metadata: HashMap::new(),
1321        }
1322    }
1323
1324    fn validator() -> CompilationValidator {
1325        CompilationValidator::new(EnhancedCrossCompilationConfig::default())
1326    }
1327
1328    #[test]
1329    fn test_validate_semantics_rejects_dissimilar_gate_content() {
1330        let v = validator();
1331
1332        // Source has gate content; target is an entirely empty compiled program.
1333        let source = make_source("h q[0]; cx q[0], q[1]; h q[1];");
1334        let target = make_target("OPENQASM 2.0;\nqreg q[2];\n", TargetPlatform::IBMQuantum);
1335
1336        let valid = v.validate_semantics(&source, &target).unwrap();
1337        assert!(
1338            !valid,
1339            "empty target code must not be judged semantically equivalent to a non-trivial source"
1340        );
1341    }
1342
1343    #[test]
1344    fn test_validate_semantics_accepts_matching_gate_content() {
1345        let v = validator();
1346
1347        let source = make_source("h q[0]; cx q[0], q[1];");
1348        let target = make_target(
1349            "OPENQASM 2.0;\nqreg q[2];\nh q[0];\ncx q[0], q[1];\n",
1350            TargetPlatform::IBMQuantum,
1351        );
1352
1353        let valid = v.validate_semantics(&source, &target).unwrap();
1354        assert!(
1355            valid,
1356            "matching gate histograms between source and target should validate as semantically consistent"
1357        );
1358    }
1359
1360    #[test]
1361    fn test_validate_semantics_both_empty_is_trivially_valid() {
1362        let v = validator();
1363        let source = make_source("// comment only, no gates");
1364        let target = make_target("// no gates emitted", TargetPlatform::Simulator);
1365
1366        let valid = v.validate_semantics(&source, &target).unwrap();
1367        assert!(valid, "two gateless programs are trivially consistent");
1368    }
1369
1370    #[test]
1371    fn test_validate_resources_rejects_oversized_circuit_for_platform() {
1372        let v = validator();
1373        // IonQ has a much smaller qubit capacity than an index of 99 implies.
1374        let target = make_target("qreg q[100];\nh q[99];\n", TargetPlatform::IonQ);
1375
1376        let valid = v.validate_resources(&target, TargetPlatform::IonQ).unwrap();
1377        assert!(
1378            !valid,
1379            "a circuit using 100 qubits must be rejected for a 32-qubit platform"
1380        );
1381    }
1382
1383    #[test]
1384    fn test_validate_resources_accepts_small_circuit() {
1385        let v = validator();
1386        let target = make_target(
1387            "qreg q[2];\nh q[0];\ncx q[0], q[1];\n",
1388            TargetPlatform::IonQ,
1389        );
1390
1391        let valid = v.validate_resources(&target, TargetPlatform::IonQ).unwrap();
1392        assert!(
1393            valid,
1394            "a 2-qubit circuit must fit within any supported platform"
1395        );
1396    }
1397
1398    #[test]
1399    fn test_estimate_fidelity_decreases_with_more_two_qubit_gates() {
1400        let v = validator();
1401        let source = make_source("h q[0]; cx q[0], q[1];");
1402
1403        let few_two_qubit_gates =
1404            make_target("h q[0];\ncx q[0], q[1];\n", TargetPlatform::IBMQuantum);
1405        let many_two_qubit_gates = make_target(
1406            "h q[0];\ncx q[0], q[1];\ncx q[1], q[0];\ncx q[0], q[1];\ncx q[1], q[0];\ncx q[0], q[1];\n",
1407            TargetPlatform::IBMQuantum,
1408        );
1409
1410        let fidelity_few = v.estimate_fidelity(&source, &few_two_qubit_gates).unwrap();
1411        let fidelity_many = v.estimate_fidelity(&source, &many_two_qubit_gates).unwrap();
1412
1413        assert!((0.0..=1.0).contains(&fidelity_few));
1414        assert!((0.0..=1.0).contains(&fidelity_many));
1415        assert!(
1416            fidelity_many < fidelity_few,
1417            "more two-qubit gates must yield a lower fidelity estimate: few={fidelity_few}, many={fidelity_many}"
1418        );
1419    }
1420
1421    #[test]
1422    fn test_estimate_fidelity_not_hardcoded_constant() {
1423        let v = validator();
1424        let source = make_source("h q[0];");
1425        let target = make_target("h q[0];\ncx q[0], q[1];\n", TargetPlatform::IonQ);
1426
1427        let fidelity = v.estimate_fidelity(&source, &target).unwrap();
1428        assert!(
1429            (fidelity - 0.99).abs() > 1e-9,
1430            "fidelity must be computed from the actual gate composition, not the old hardcoded 0.99"
1431        );
1432    }
1433
1434    #[test]
1435    fn test_validate_compilation_can_actually_fail() {
1436        let v = validator();
1437        let source = make_source("h q[0]; cx q[0], q[1]; h q[1]; cx q[1], q[0];");
1438        // Deliberately mismatched / oversized target for IonQ.
1439        let target = make_target("qreg q[64];\n", TargetPlatform::IonQ);
1440
1441        let result = v
1442            .validate_compilation(&source, &target, TargetPlatform::IonQ)
1443            .unwrap();
1444        assert!(
1445            !result.is_valid,
1446            "comprehensive validation must be able to reject a broken/mismatched compilation"
1447        );
1448    }
1449
1450    // -----------------------------------------------------------------------
1451    // CompilationFeatureExtractor / CompilationModel tests
1452    // -----------------------------------------------------------------------
1453
1454    #[test]
1455    fn test_extract_features_reflects_actual_circuit_content() {
1456        let extractor = CompilationFeatureExtractor::new();
1457        let ir = build_ir(
1458            2,
1459            vec![
1460                single_gate(IRGate::H, 0),
1461                single_gate(IRGate::RX(0.5), 0),
1462                two_qubit_gate(IRGate::CNOT, 0, 1),
1463            ],
1464        );
1465
1466        let features = extractor
1467            .extract_features(&ir, TargetPlatform::IBMQuantum)
1468            .unwrap();
1469
1470        assert_eq!(features.circuit_features.len(), 7);
1471        assert_eq!(features.circuit_features[0], 2.0, "num_qubits");
1472        assert_eq!(features.circuit_features[1], 3.0, "total_gates");
1473        assert_eq!(features.circuit_features[3], 1.0, "two_qubit_gates");
1474        assert_eq!(features.circuit_features[5], 1.0, "rotation_gates");
1475        assert!(!features.target_features.is_empty());
1476        assert!(!features.complexity_features.is_empty());
1477
1478        let empty_ir = build_ir(1, vec![]);
1479        let empty_features = extractor
1480            .extract_features(&empty_ir, TargetPlatform::IBMQuantum)
1481            .unwrap();
1482        assert_ne!(
1483            features.circuit_features, empty_features.circuit_features,
1484            "feature vectors must depend on the actual circuit content"
1485        );
1486    }
1487
1488    #[test]
1489    fn test_predict_strategy_selects_real_transformations() {
1490        let model = CompilationModel::new();
1491        let extractor = CompilationFeatureExtractor::new();
1492
1493        let ir_with_rotations = build_ir(
1494            1,
1495            vec![
1496                single_gate(IRGate::RX(0.5), 0),
1497                single_gate(IRGate::RX(0.5), 0),
1498            ],
1499        );
1500        let features = extractor
1501            .extract_features(&ir_with_rotations, TargetPlatform::IBMQuantum)
1502            .unwrap();
1503        let strategy = model.predict_strategy(&features).unwrap();
1504        assert!(
1505            !strategy.transformations.is_empty(),
1506            "a circuit with rotation gates must yield a non-empty strategy"
1507        );
1508        assert!(
1509            strategy
1510                .transformations
1511                .iter()
1512                .any(|t| matches!(t.transform_type, TransformationType::RotationMerging)),
1513            "rotation-heavy circuits should select RotationMerging"
1514        );
1515
1516        let empty_ir = build_ir(1, vec![]);
1517        let empty_features = extractor
1518            .extract_features(&empty_ir, TargetPlatform::IBMQuantum)
1519            .unwrap();
1520        let empty_strategy = model.predict_strategy(&empty_features).unwrap();
1521        assert!(
1522            empty_strategy.transformations.is_empty(),
1523            "an empty circuit should not select any transformations"
1524        );
1525        assert!((empty_strategy.confidence - 0.5).abs() < 1e-9);
1526    }
1527}