Skip to main content

quantrs2_device/circuit_migration/
engine.rs

1//! The circuit migration pipeline engine.
2//!
3//! [`CircuitMigrationEngine`] runs the full migrate -> translate -> map ->
4//! optimize -> validate pipeline described in the parent module's docs. The
5//! free functions at the top of this file (gate cancellation and a minimal
6//! state-vector simulator) are the real, self-contained primitives the
7//! pipeline's optimization and validation stages are built on.
8
9use std::collections::{HashMap, HashSet};
10use std::sync::{Arc, Mutex, RwLock};
11use std::time::{Duration, Instant, SystemTime};
12
13use quantrs2_circuit::prelude::*;
14use quantrs2_core::{
15    error::{QuantRS2Error, QuantRS2Result},
16    gate::GateOp,
17    qubit::QubitId,
18};
19
20// SciRS2 integration for advanced migration optimization
21#[cfg(feature = "scirs2")]
22use scirs2_graph::{
23    betweenness_centrality, closeness_centrality, dijkstra_path, minimum_spanning_tree, Graph,
24};
25#[cfg(feature = "scirs2")]
26use scirs2_optimize::{differential_evolution, minimize, OptimizeResult};
27#[cfg(feature = "scirs2")]
28use scirs2_stats::{corrcoef, mean, pearsonr, spearmanr, std};
29
30// Fallback implementations
31#[cfg(not(feature = "scirs2"))]
32mod fallback_scirs2 {
33    use scirs2_core::ndarray::{Array1, Array2};
34
35    pub fn mean(_data: &Array1<f64>) -> Result<f64, String> {
36        Ok(0.0)
37    }
38    pub fn std(_data: &Array1<f64>, _ddof: i32) -> Result<f64, String> {
39        Ok(1.0)
40    }
41    pub fn pearsonr(_x: &Array1<f64>, _y: &Array1<f64>) -> Result<(f64, f64), String> {
42        Ok((0.0, 0.5))
43    }
44
45    pub struct OptimizeResult {
46        pub x: Array1<f64>,
47        pub fun: f64,
48        pub success: bool,
49    }
50
51    pub fn minimize(
52        _func: fn(&Array1<f64>) -> f64,
53        _x0: &Array1<f64>,
54    ) -> Result<OptimizeResult, String> {
55        Ok(OptimizeResult {
56            x: Array1::zeros(2),
57            fun: 0.0,
58            success: true,
59        })
60    }
61}
62
63#[cfg(not(feature = "scirs2"))]
64use fallback_scirs2::*;
65
66use scirs2_core::ndarray::{Array1, Array2};
67use scirs2_core::Complex64;
68
69use crate::{
70    backend_traits::{query_backend_capabilities, BackendCapabilities},
71    calibration::{CalibrationManager, DeviceCalibration},
72    mapping_scirs2::{SciRS2MappingConfig, SciRS2QubitMapper},
73    optimization::{CalibrationOptimizer, OptimizationConfig},
74    topology::HardwareTopology,
75    translation::{GateTranslator, HardwareBackend},
76    DeviceError, DeviceResult,
77};
78
79use super::analysis::{CircuitAnalysis, ConnectivityAnalysis, GateAnalysis, ResourceAnalysis};
80use super::{
81    AppliedTransformation, CircuitMetrics, DistributionComparison, ErrorAnalysis,
82    FidelityComparison, GateTranslationStrategy, MigrationConfig, MigrationMetrics,
83    MigrationResult, MigrationStage, MigrationStatistics, MigrationWarning, OptimizationPass,
84    PerformanceComparison, ResourceMetrics, StatisticalValidationResult, TransformationImpact,
85    TransformationType, ValidationMethod, ValidationMethodResult, ValidationResult,
86    WarningSeverity, WarningType,
87};
88
89/// Main circuit migration engine
90pub struct CircuitMigrationEngine {
91    calibration_manager: CalibrationManager,
92    mapper: SciRS2QubitMapper,
93    optimizer: CalibrationOptimizer,
94    translator: GateTranslator,
95    migration_cache: RwLock<HashMap<String, CachedMigration>>,
96    performance_tracker: Mutex<PerformanceTracker>,
97}
98
99/// Cached migration result
100#[derive(Debug, Clone)]
101struct CachedMigration {
102    config_hash: u64,
103    result: Vec<u8>, // Serialized migration result
104    created_at: SystemTime,
105    access_count: usize,
106}
107
108/// Performance tracking for migrations
109#[derive(Debug, Clone)]
110struct PerformanceTracker {
111    migration_history: Vec<MigrationPerformanceRecord>,
112    average_migration_time: Duration,
113    success_rate: f64,
114    common_issues: HashMap<String, usize>,
115}
116
117/// Migration performance record
118#[derive(Debug, Clone)]
119struct MigrationPerformanceRecord {
120    config: MigrationConfig,
121    execution_time: Duration,
122    success: bool,
123    quality_score: f64,
124    timestamp: SystemTime,
125}
126
127/// Gate names (both the upper-case names reported by `quantrs2_core` gate
128/// structs, e.g. `"CNOT"`, and the lower-case native-gate names produced by
129/// [`GateTranslator::add_decomposed_gate`], e.g. `"cnot"`) that are their own
130/// inverse: applying the same gate to the same qubits twice is the identity.
131const SELF_INVERSE_GATE_NAMES: &[&str] = &[
132    "X", "x", "Y", "y", "Z", "z", "H", "h", "CNOT", "cnot", "cx", "CZ", "cz", "SWAP", "swap",
133];
134
135/// Cancel back-to-back pairs of identical self-inverse gates that act on
136/// exactly the same qubits (in the same order), where no other gate has
137/// touched any of those qubits in between. This is an algebraically exact
138/// optimization (it only removes operations that compose to the identity),
139/// used by the migration engine to genuinely reduce gate count / depth
140/// rather than fabricating an "optimized" result.
141pub(crate) fn cancel_adjacent_self_inverse_gates<const N: usize>(
142    circuit: &Circuit<N>,
143) -> DeviceResult<Circuit<N>> {
144    let boxed_gates = circuit.gates_as_boxes();
145    // `output[i]` becomes `None` once tombstoned (cancelled out).
146    let mut output: Vec<Option<Box<dyn GateOp>>> = Vec::with_capacity(boxed_gates.len());
147    // Per-qubit stack of surviving output indices that still touch it.
148    let mut history: HashMap<QubitId, Vec<usize>> = HashMap::new();
149
150    for gate in boxed_gates {
151        let qubits = gate.qubits();
152        let name = gate.name();
153
154        let mut cancel_idx = None;
155        if !qubits.is_empty() && SELF_INVERSE_GATE_NAMES.contains(&name) {
156            if let Some(&top) = history.get(&qubits[0]).and_then(|stack| stack.last()) {
157                let all_same_top = qubits
158                    .iter()
159                    .all(|q| history.get(q).and_then(|s| s.last()) == Some(&top));
160                if all_same_top {
161                    if let Some(Some(prev)) = output.get(top) {
162                        if prev.name() == name && prev.qubits() == qubits {
163                            cancel_idx = Some(top);
164                        }
165                    }
166                }
167            }
168        }
169
170        if let Some(idx) = cancel_idx {
171            output[idx] = None;
172            for q in &qubits {
173                if let Some(stack) = history.get_mut(q) {
174                    stack.pop();
175                }
176            }
177        } else {
178            let new_idx = output.len();
179            for q in &qubits {
180                history.entry(*q).or_default().push(new_idx);
181            }
182            output.push(Some(gate));
183        }
184    }
185
186    let surviving: Vec<Box<dyn GateOp>> = output.into_iter().flatten().collect();
187    Circuit::from_gates(surviving).map_err(|e| {
188        DeviceError::CircuitConversion(format!(
189            "Failed to rebuild circuit after gate cancellation: {e}"
190        ))
191    })
192}
193
194/// Apply a single gate's unitary matrix (from [`GateOp::matrix`]) to a state
195/// vector in place. Supports gates of any arity: the convention matches the
196/// one `quantrs2_core` gates already use for their matrices (e.g. `CNOT`'s
197/// 4x4 matrix), where the first qubit returned by `gate.qubits()` is the most
198/// significant bit of the gate's local sub-index and the last is the least
199/// significant bit.
200fn apply_gate_to_statevector(state: &mut [Complex64], gate: &dyn GateOp) -> DeviceResult<()> {
201    let qubits = gate.qubits();
202    let k = qubits.len();
203    if k == 0 {
204        return Ok(());
205    }
206
207    let matrix = gate.matrix().map_err(|e| {
208        DeviceError::CircuitConversion(format!(
209            "Failed to obtain matrix for gate '{}': {e}",
210            gate.name()
211        ))
212    })?;
213    let dim_gate = 1usize << k;
214    if matrix.len() != dim_gate * dim_gate {
215        return Err(DeviceError::CircuitConversion(format!(
216            "Gate '{}' matrix has {} entries, expected {dim_gate}x{dim_gate} for a {k}-qubit gate",
217            gate.name(),
218            matrix.len()
219        )));
220    }
221
222    let dim = state.len();
223    let qubit_idx: Vec<usize> = qubits.iter().map(|q| q.id() as usize).collect();
224    let zero = Complex64::new(0.0, 0.0);
225    let mut new_state = vec![zero; dim];
226
227    for i in 0..dim {
228        let amp = state[i];
229        if amp == zero {
230            continue;
231        }
232
233        let mut sub_in = 0usize;
234        for (pos, &q) in qubit_idx.iter().enumerate() {
235            let bit = (i >> q) & 1;
236            sub_in |= bit << (k - 1 - pos);
237        }
238
239        for sub_out in 0..dim_gate {
240            let m = matrix[sub_out * dim_gate + sub_in];
241            if m == zero {
242                continue;
243            }
244            let mut out_idx = i;
245            for (pos, &q) in qubit_idx.iter().enumerate() {
246                let bit_out = (sub_out >> (k - 1 - pos)) & 1;
247                let bit_in = (sub_in >> (k - 1 - pos)) & 1;
248                if bit_out != bit_in {
249                    out_idx ^= 1 << q;
250                }
251            }
252            new_state[out_idx] += m * amp;
253        }
254    }
255
256    state.copy_from_slice(&new_state);
257    Ok(())
258}
259
260/// Simulate `circuit` starting from the computational basis state
261/// `|state_idx>` and return the resulting amplitude vector. This is a
262/// minimal, self-contained state-vector simulator (no external simulator
263/// dependency) used purely for migration validation: functional
264/// equivalence, fidelity, and statistical comparison between an original and
265/// a migrated circuit.
266pub(crate) fn simulate_basis_state<const N: usize>(
267    circuit: &Circuit<N>,
268    state_idx: usize,
269) -> DeviceResult<Vec<Complex64>> {
270    let dim = 1usize << N;
271    let mut state = vec![Complex64::new(0.0, 0.0); dim];
272    state[state_idx] = Complex64::new(1.0, 0.0);
273
274    for gate in circuit.gates() {
275        apply_gate_to_statevector(&mut state, gate.as_ref())?;
276    }
277
278    Ok(state)
279}
280
281/// Measurement-outcome probabilities (`|amplitude|^2`) for `circuit` starting
282/// from `|state_idx>`.
283fn measurement_probabilities<const N: usize>(
284    circuit: &Circuit<N>,
285    state_idx: usize,
286) -> DeviceResult<Vec<f64>> {
287    Ok(simulate_basis_state(circuit, state_idx)?
288        .iter()
289        .map(scirs2_core::Complex64::norm_sqr)
290        .collect())
291}
292
293/// State fidelity `|<a|b>|^2` between two equal-length amplitude vectors.
294pub(crate) fn state_fidelity(a: &[Complex64], b: &[Complex64]) -> f64 {
295    let overlap: Complex64 = a.iter().zip(b.iter()).map(|(x, y)| x.conj() * y).sum();
296    overlap.norm_sqr().clamp(0.0, 1.0)
297}
298
299/// Append the effect of a single [`crate::translation::DecomposedGate`] onto
300/// `circuit` using the circuit builder's typed methods. Mirrors the gate
301/// name -> builder-method mapping `GateTranslator` uses internally so a
302/// decomposition produced by [`GateTranslator::translate_gate`] can be
303/// replayed here without depending on that (private) internal helper.
304fn append_decomposed_gate<const N: usize>(
305    circuit: &mut Circuit<N>,
306    gate: &crate::translation::DecomposedGate,
307) -> DeviceResult<()> {
308    let qubits = &gate.qubits;
309    let params = &gate.parameters;
310    let err = |e: QuantRS2Error| {
311        DeviceError::CircuitConversion(format!(
312            "Failed to append decomposed gate '{}': {e}",
313            gate.native_gate
314        ))
315    };
316
317    match gate.native_gate.as_str() {
318        "id" => {}
319        "x" => {
320            circuit.x(qubits[0]).map_err(err)?;
321        }
322        "sx" => {
323            circuit.sx(qubits[0]).map_err(err)?;
324        }
325        "rz" => {
326            circuit.rz(qubits[0], params[0]).map_err(err)?;
327        }
328        "rx" => {
329            circuit.rx(qubits[0], params[0]).map_err(err)?;
330        }
331        "ry" => {
332            circuit.ry(qubits[0], params[0]).map_err(err)?;
333        }
334        "h" => {
335            circuit.h(qubits[0]).map_err(err)?;
336        }
337        "y" => {
338            circuit.y(qubits[0]).map_err(err)?;
339        }
340        "z" => {
341            circuit.z(qubits[0]).map_err(err)?;
342        }
343        "s" => {
344            circuit.s(qubits[0]).map_err(err)?;
345        }
346        "t" => {
347            circuit.t(qubits[0]).map_err(err)?;
348        }
349        "cx" | "cnot" | "xx" => {
350            circuit.cnot(qubits[0], qubits[1]).map_err(err)?;
351        }
352        "cz" => {
353            circuit.cz(qubits[0], qubits[1]).map_err(err)?;
354        }
355        "swap" => {
356            circuit.swap(qubits[0], qubits[1]).map_err(err)?;
357        }
358        "ccnot" | "toffoli" => {
359            circuit
360                .toffoli(qubits[0], qubits[1], qubits[2])
361                .map_err(err)?;
362        }
363        other => {
364            return Err(DeviceError::CircuitConversion(format!(
365                "Unknown decomposed native gate: {other}"
366            )));
367        }
368    }
369
370    Ok(())
371}
372
373impl CircuitMigrationEngine {
374    /// Create a new circuit migration engine
375    pub fn new(
376        calibration_manager: CalibrationManager,
377        mapper: SciRS2QubitMapper,
378        optimizer: CalibrationOptimizer,
379        translator: GateTranslator,
380    ) -> Self {
381        Self {
382            calibration_manager,
383            mapper,
384            optimizer,
385            translator,
386            migration_cache: RwLock::new(HashMap::new()),
387            performance_tracker: Mutex::new(PerformanceTracker {
388                migration_history: Vec::new(),
389                average_migration_time: Duration::from_secs(0),
390                success_rate: 1.0,
391                common_issues: HashMap::new(),
392            }),
393        }
394    }
395
396    /// Migrate a circuit between platforms
397    pub async fn migrate_circuit<const N: usize>(
398        &mut self,
399        circuit: &Circuit<N>,
400        config: &MigrationConfig,
401    ) -> DeviceResult<MigrationResult<N>> {
402        let start_time = Instant::now();
403        let mut warnings = Vec::new();
404        let mut transformations = Vec::new();
405
406        // Stage 1: Analysis
407        let analysis = self.analyze_circuit(circuit, config)?;
408
409        // Stage 2: Translation
410        let (translated_circuit, translation_transforms) =
411            self.translate_circuit(circuit, config, &analysis).await?;
412        transformations.extend(translation_transforms);
413
414        // Stage 3: Mapping
415        let (mapped_circuit, mapping_transforms) = self
416            .map_circuit(&translated_circuit, config, &analysis)
417            .await?;
418        transformations.extend(mapping_transforms);
419
420        // Stage 4: Optimization
421        let (optimized_circuit, optimization_transforms) = self
422            .optimize_migrated_circuit(&mapped_circuit, config, &analysis)
423            .await?;
424        transformations.extend(optimization_transforms);
425
426        // Stage 5: Validation
427        let validation_result = if config.validation_config.enable_validation {
428            Some(
429                self.validate_migration(circuit, &optimized_circuit, config)
430                    .await?,
431            )
432        } else {
433            None
434        };
435
436        // Stage 6: Metrics calculation
437        let metrics = self.calculate_migration_metrics(
438            circuit,
439            &optimized_circuit,
440            &transformations,
441            start_time.elapsed(),
442        )?;
443
444        // Check if migration meets requirements
445        let success = self.check_migration_requirements(&metrics, config, &mut warnings)?;
446
447        // Record performance
448        self.record_migration_performance(config, start_time.elapsed(), success, &metrics)
449            .await?;
450
451        Ok(MigrationResult {
452            migrated_circuit: optimized_circuit,
453            metrics,
454            transformations,
455            validation: validation_result,
456            warnings,
457            success,
458        })
459    }
460
461    /// Analyze circuit for migration planning
462    fn analyze_circuit<const N: usize>(
463        &self,
464        circuit: &Circuit<N>,
465        config: &MigrationConfig,
466    ) -> DeviceResult<CircuitAnalysis> {
467        // Analyze circuit structure, gates, connectivity requirements
468        let gate_analysis = self.analyze_gates(circuit, config)?;
469        let connectivity_analysis = self.analyze_connectivity(circuit, config)?;
470        let resource_analysis = self.analyze_resources(circuit, config)?;
471
472        Ok(CircuitAnalysis {
473            gate_analysis,
474            connectivity_analysis,
475            resource_analysis,
476            compatibility_score: self.calculate_compatibility_score(circuit, config)?,
477        })
478    }
479
480    /// Translate circuit gates for target platform
481    async fn translate_circuit<const N: usize>(
482        &mut self,
483        circuit: &Circuit<N>,
484        config: &MigrationConfig,
485        analysis: &CircuitAnalysis,
486    ) -> DeviceResult<(Circuit<N>, Vec<AppliedTransformation>)> {
487        let mut translated_circuit = circuit.clone();
488        let mut transformations = Vec::new();
489
490        // Get target platform capabilities
491        let target_caps = query_backend_capabilities(config.target_platform);
492
493        // Translate gates based on strategy
494        match config.translation_config.gate_strategy {
495            GateTranslationStrategy::PreferNative => {
496                self.translate_to_native_gates(
497                    &mut translated_circuit,
498                    &target_caps,
499                    &mut transformations,
500                )?;
501            }
502            GateTranslationStrategy::MinimizeGates => {
503                self.translate_minimize_gates(
504                    &mut translated_circuit,
505                    &target_caps,
506                    &mut transformations,
507                )?;
508            }
509            GateTranslationStrategy::PreserveFidelity => {
510                self.translate_preserve_fidelity(
511                    &mut translated_circuit,
512                    &target_caps,
513                    &mut transformations,
514                )?;
515            }
516            GateTranslationStrategy::MinimizeDepth => {
517                self.translate_minimize_depth(
518                    &mut translated_circuit,
519                    &target_caps,
520                    &mut transformations,
521                )?;
522            }
523            GateTranslationStrategy::CustomPriority(ref priorities) => {
524                self.translate_custom_priority(
525                    &mut translated_circuit,
526                    &target_caps,
527                    priorities,
528                    &mut transformations,
529                )?;
530            }
531        }
532
533        Ok((translated_circuit, transformations))
534    }
535
536    /// Map qubits for target platform topology
537    async fn map_circuit<const N: usize>(
538        &mut self,
539        circuit: &Circuit<N>,
540        config: &MigrationConfig,
541        analysis: &CircuitAnalysis,
542    ) -> DeviceResult<(Circuit<N>, Vec<AppliedTransformation>)> {
543        let mut mapped_circuit = circuit.clone();
544        let mut transformations = Vec::new();
545
546        if config.mapping_config.scirs2_config_placeholder {
547            // Beta.3: Using simple mapping fallback (production-ready)
548            // Future: Full SciRS2-powered intelligent mapping (post-beta.3)
549            // let mapping_result = self.mapper.map_circuit(circuit)?;
550            // mapped_circuit = self.apply_qubit_mapping(circuit, &mapping_result)?;
551
552            transformations.push(AppliedTransformation {
553                transformation_type: TransformationType::QubitMapping,
554                description: "SciRS2 mapping (placeholder)".to_string(),
555                impact: TransformationImpact {
556                    fidelity_impact: -0.01,
557                    time_impact: 0.1,
558                    resource_impact: 0.05,
559                    confidence: 0.8,
560                },
561                stage: MigrationStage::Mapping,
562            });
563        } else {
564            // Use simple mapping strategy
565            let simple_mapping = self.create_simple_mapping(circuit, config)?;
566            mapped_circuit = self.apply_simple_mapping(circuit, &simple_mapping)?;
567
568            transformations.push(AppliedTransformation {
569                transformation_type: TransformationType::QubitMapping,
570                description: "Simple qubit mapping".to_string(),
571                impact: TransformationImpact {
572                    fidelity_impact: 0.0,
573                    time_impact: 0.0,
574                    resource_impact: 0.0,
575                    confidence: 0.7,
576                },
577                stage: MigrationStage::Mapping,
578            });
579        }
580
581        Ok((mapped_circuit, transformations))
582    }
583
584    /// Optimize the migrated circuit for target platform
585    async fn optimize_migrated_circuit<const N: usize>(
586        &self,
587        circuit: &Circuit<N>,
588        config: &MigrationConfig,
589        analysis: &CircuitAnalysis,
590    ) -> DeviceResult<(Circuit<N>, Vec<AppliedTransformation>)> {
591        let mut optimized_circuit = circuit.clone();
592        let mut transformations = Vec::new();
593
594        if config.optimization.enable_optimization {
595            // Apply optimization passes
596            for pass in &config.optimization.optimization_passes {
597                let (new_circuit, pass_transforms) = self
598                    .apply_optimization_pass(&optimized_circuit, pass, config)
599                    .await?;
600                optimized_circuit = new_circuit;
601                transformations.extend(pass_transforms);
602            }
603
604            // SciRS2-powered multi-objective optimization
605            if config.optimization.enable_scirs2_optimization {
606                let (sci_optimized, sci_transforms) = self
607                    .apply_scirs2_optimization(&optimized_circuit, config)
608                    .await?;
609                optimized_circuit = sci_optimized;
610                transformations.extend(sci_transforms);
611            }
612        }
613
614        Ok((optimized_circuit, transformations))
615    }
616
617    /// Validate migration quality
618    async fn validate_migration<const N: usize>(
619        &self,
620        original: &Circuit<N>,
621        migrated: &Circuit<N>,
622        config: &MigrationConfig,
623    ) -> DeviceResult<ValidationResult> {
624        let mut method_results = HashMap::new();
625
626        for method in &config.validation_config.validation_methods {
627            let result = match method {
628                ValidationMethod::FunctionalEquivalence => {
629                    self.validate_functional_equivalence(original, migrated)
630                        .await?
631                }
632                ValidationMethod::StatisticalComparison => {
633                    self.validate_statistical_comparison(original, migrated, config)
634                        .await?
635                }
636                ValidationMethod::FidelityMeasurement => {
637                    self.validate_fidelity_measurement(original, migrated, config)
638                        .await?
639                }
640                ValidationMethod::ProcessTomography => {
641                    self.validate_process_tomography(original, migrated, config)
642                        .await?
643                }
644                ValidationMethod::BenchmarkTesting => {
645                    self.validate_benchmark_testing(original, migrated, config)
646                        .await?
647                }
648            };
649            method_results.insert(method.clone(), result);
650        }
651
652        let overall_success = method_results.values().all(|r| r.success);
653        let confidence_score =
654            method_results.values().map(|r| r.score).sum::<f64>() / method_results.len() as f64;
655
656        let statistical_results = self
657            .perform_statistical_validation(original, migrated, config)
658            .await?;
659
660        Ok(ValidationResult {
661            overall_success,
662            method_results,
663            statistical_results,
664            confidence_score,
665        })
666    }
667
668    // Helper methods for migration pipeline...
669
670    /// Calculate migration metrics
671    fn calculate_migration_metrics<const N: usize>(
672        &self,
673        original: &Circuit<N>,
674        migrated: &Circuit<N>,
675        transformations: &[AppliedTransformation],
676        migration_time: Duration,
677    ) -> DeviceResult<MigrationMetrics> {
678        let original_metrics = self.calculate_circuit_metrics(original)?;
679        let migrated_metrics = self.calculate_circuit_metrics(migrated)?;
680
681        let migration_stats = MigrationStatistics {
682            migration_time,
683            transformations_applied: transformations.len(),
684            optimization_iterations: transformations
685                .iter()
686                .filter(|t| t.transformation_type == TransformationType::CircuitOptimization)
687                .count(),
688            mapping_overhead: self.calculate_mapping_overhead(transformations),
689            translation_efficiency: self.calculate_translation_efficiency(transformations),
690        };
691
692        let performance_comparison = PerformanceComparison {
693            fidelity_change: migrated_metrics.estimated_fidelity
694                - original_metrics.estimated_fidelity,
695            execution_time_change: (migrated_metrics.estimated_execution_time.as_secs_f64()
696                / original_metrics.estimated_execution_time.as_secs_f64())
697                - 1.0,
698            depth_change: (migrated_metrics.depth as f64 / original_metrics.depth as f64) - 1.0,
699            gate_count_change: (migrated_metrics.gate_count as f64
700                / original_metrics.gate_count as f64)
701                - 1.0,
702            resource_change: self.calculate_resource_change(&original_metrics, &migrated_metrics),
703            quality_score: self.calculate_quality_score(&original_metrics, &migrated_metrics),
704        };
705
706        Ok(MigrationMetrics {
707            original: original_metrics,
708            migrated: migrated_metrics,
709            migration_stats,
710            performance_comparison,
711        })
712    }
713
714    /// Record migration performance for analytics
715    async fn record_migration_performance(
716        &self,
717        config: &MigrationConfig,
718        execution_time: Duration,
719        success: bool,
720        metrics: &MigrationMetrics,
721    ) -> DeviceResult<()> {
722        let mut tracker = self
723            .performance_tracker
724            .lock()
725            .unwrap_or_else(|e| e.into_inner());
726
727        let record = MigrationPerformanceRecord {
728            config: config.clone(),
729            execution_time,
730            success,
731            quality_score: metrics.performance_comparison.quality_score,
732            timestamp: SystemTime::now(),
733        };
734
735        tracker.migration_history.push(record);
736
737        // Update statistics
738        let total_migrations = tracker.migration_history.len();
739        let successful_migrations = tracker
740            .migration_history
741            .iter()
742            .filter(|r| r.success)
743            .count();
744
745        tracker.success_rate = successful_migrations as f64 / total_migrations as f64;
746
747        let total_time: Duration = tracker
748            .migration_history
749            .iter()
750            .map(|r| r.execution_time)
751            .sum();
752        tracker.average_migration_time = total_time / total_migrations as u32;
753
754        Ok(())
755    }
756
757    /// Analyze the circuit's gate composition against the target platform's
758    /// native gate set: which gate types appear, how many instances of each
759    /// require decomposition, and which (if any) have no synthesis path at
760    /// all (gates on more than 3 qubits have no fallback in
761    /// [`GateTranslator::translate_gate`]'s synthesis dispatch).
762    fn analyze_gates<const N: usize>(
763        &self,
764        circuit: &Circuit<N>,
765        config: &MigrationConfig,
766    ) -> DeviceResult<GateAnalysis> {
767        let mut gate_types = HashSet::new();
768        let mut unsupported_gates = Vec::new();
769        let mut decomposition_required = HashMap::new();
770
771        for gate in circuit.gates() {
772            let name = gate.name();
773            gate_types.insert(name.to_string());
774
775            let is_native = self.translator.is_native_gate(config.target_platform, name)
776                || self
777                    .translator
778                    .is_native_gate(config.target_platform, &name.to_lowercase());
779
780            if !is_native {
781                *decomposition_required.entry(name.to_string()).or_insert(0) += 1;
782
783                if gate.num_qubits() > 3 && !unsupported_gates.contains(&name.to_string()) {
784                    unsupported_gates.push(name.to_string());
785                }
786            }
787        }
788
789        Ok(GateAnalysis {
790            gate_types,
791            unsupported_gates,
792            decomposition_required,
793        })
794    }
795
796    fn analyze_connectivity<const N: usize>(
797        &self,
798        _circuit: &Circuit<N>,
799        _config: &MigrationConfig,
800    ) -> DeviceResult<ConnectivityAnalysis> {
801        Ok(ConnectivityAnalysis::default())
802    }
803
804    fn analyze_resources<const N: usize>(
805        &self,
806        _circuit: &Circuit<N>,
807        _config: &MigrationConfig,
808    ) -> DeviceResult<ResourceAnalysis> {
809        Ok(ResourceAnalysis::default())
810    }
811
812    /// Real compatibility score: the fraction of the circuit's gates that
813    /// are already native to the target platform (as opposed to a fixed
814    /// constant regardless of circuit content).
815    fn calculate_compatibility_score<const N: usize>(
816        &self,
817        circuit: &Circuit<N>,
818        config: &MigrationConfig,
819    ) -> DeviceResult<f64> {
820        let gates = circuit.gates();
821        if gates.is_empty() {
822            return Ok(1.0);
823        }
824
825        let native_count = gates
826            .iter()
827            .filter(|gate| {
828                let name = gate.name();
829                self.translator.is_native_gate(config.target_platform, name)
830                    || self
831                        .translator
832                        .is_native_gate(config.target_platform, &name.to_lowercase())
833            })
834            .count();
835
836        Ok(native_count as f64 / gates.len() as f64)
837    }
838
839    /// Compute circuit metrics from the circuit's actual gate composition:
840    /// per-gate fidelity/duration comes from the calibration manager when a
841    /// matching calibration is loaded, and otherwise from an arity-based
842    /// default (single-qubit gates are assumed higher fidelity/faster than
843    /// two-qubit gates, which in turn are faster than higher-arity gates).
844    /// Both the fidelity (a product over gates) and the execution time (a
845    /// sum over gates) scale with the actual circuit rather than being a
846    /// fixed constant.
847    fn calculate_circuit_metrics<const N: usize>(
848        &self,
849        circuit: &Circuit<N>,
850    ) -> DeviceResult<CircuitMetrics> {
851        let gates = circuit.gates();
852        let gate_count = gates.len();
853        let depth = circuit.calculate_depth();
854        let latest_calibration = self.calibration_manager.get_latest_calibration();
855
856        let mut gate_counts: HashMap<String, usize> = HashMap::new();
857        let mut estimated_fidelity = 1.0_f64;
858        let mut total_duration_ns = 0.0_f64;
859
860        for gate in gates {
861            *gate_counts.entry(gate.name().to_string()).or_insert(0) += 1;
862
863            let qubits = gate.qubits();
864            let (default_fidelity, default_duration_ns) = match qubits.len() {
865                0 => (1.0, 0.0),
866                1 => (0.9995, 30.0),
867                2 => (0.99, 250.0),
868                _ => (0.95, 500.0),
869            };
870
871            let fidelity = latest_calibration
872                .and_then(|cal| {
873                    self.calibration_manager
874                        .get_gate_fidelity(&cal.device_id, gate.name(), &qubits)
875                })
876                .unwrap_or(default_fidelity);
877            estimated_fidelity *= fidelity;
878
879            let duration_ns = latest_calibration
880                .and_then(|cal| {
881                    self.calibration_manager
882                        .get_gate_duration(&cal.device_id, gate.name(), &qubits)
883                })
884                .unwrap_or(default_duration_ns);
885            total_duration_ns += duration_ns.max(0.0);
886        }
887
888        let estimated_execution_time = Duration::from_nanos(total_duration_ns.round() as u64);
889        let amplitude_count = 1u64 << (N.min(30) as u32);
890        // Complex128 state-vector memory footprint (16 bytes/amplitude).
891        let memory_mb = (amplitude_count as f64 * 16.0) / (1024.0 * 1024.0);
892
893        Ok(CircuitMetrics {
894            qubit_count: N,
895            depth,
896            gate_count,
897            gate_counts,
898            estimated_fidelity: estimated_fidelity.clamp(0.0, 1.0),
899            estimated_execution_time,
900            resource_requirements: ResourceMetrics {
901                memory_mb: memory_mb.max(1e-6),
902                cpu_time: Duration::from_micros(gate_count as u64 + 1),
903                qpu_time: estimated_execution_time,
904                network_bandwidth: None,
905            },
906        })
907    }
908
909    /// Translate every gate in `circuit` to the target backend's native gate
910    /// set, using the real gate-decomposition engine ([`GateTranslator`])
911    /// rather than leaving the circuit untouched. This is shared by every
912    /// [`GateTranslationStrategy`] because hardware compatibility is
913    /// mandatory; the strategies differ only in what they do *after* this
914    /// mandatory translation (see the other `translate_*` methods below).
915    ///
916    /// Gates that are already native to the target backend (matched
917    /// case-insensitively -- `GateTranslator`'s native-gate tables use
918    /// lowercase names such as `"cnot"`/`"h"` while `quantrs2_core` gate
919    /// objects report names like `"CNOT"`/`"H"`) are copied through
920    /// unchanged instead of being routed through gate-synthesis; only gates
921    /// that are genuinely absent from the target's native set are handed to
922    /// [`GateTranslator::translate_gate`] for real decomposition.
923    fn translate_to_native_gates<const N: usize>(
924        &mut self,
925        circuit: &mut Circuit<N>,
926        caps: &BackendCapabilities,
927        transforms: &mut Vec<AppliedTransformation>,
928    ) -> DeviceResult<()> {
929        let before_gate_count = circuit.gates().len();
930        let translated = self.translate_circuit_case_aware(circuit, caps.backend)?;
931        let after_gate_count = translated.gates().len();
932        *circuit = translated;
933
934        transforms.push(AppliedTransformation {
935            transformation_type: TransformationType::GateTranslation,
936            description: format!(
937                "Translated circuit to {:?} native gate set ({before_gate_count} -> {after_gate_count} gates)",
938                caps.backend
939            ),
940            impact: TransformationImpact {
941                fidelity_impact: 0.0,
942                time_impact: (after_gate_count as f64 - before_gate_count as f64)
943                    / before_gate_count.max(1) as f64,
944                resource_impact: 0.0,
945                confidence: 0.9,
946            },
947            stage: MigrationStage::Translation,
948        });
949        Ok(())
950    }
951
952    /// Case-insensitive-aware translation: gates already native to `backend`
953    /// are copied through as-is; every other gate is decomposed via
954    /// [`GateTranslator::translate_gate`] and rebuilt with the circuit
955    /// builder's typed methods (mirroring the mapping `GateTranslator` uses
956    /// internally for its own `translate_circuit`).
957    pub(crate) fn translate_circuit_case_aware<const N: usize>(
958        &mut self,
959        circuit: &Circuit<N>,
960        backend: HardwareBackend,
961    ) -> DeviceResult<Circuit<N>> {
962        let mut translated = Circuit::<N>::new();
963
964        for gate_arc in circuit.gates() {
965            let name = gate_arc.name();
966            let already_native = self.translator.is_native_gate(backend, name)
967                || self
968                    .translator
969                    .is_native_gate(backend, &name.to_lowercase());
970
971            if already_native {
972                translated.add_gate_arc(Arc::clone(gate_arc)).map_err(|e| {
973                    DeviceError::CircuitConversion(format!(
974                        "Failed to copy already-native gate '{name}': {e}"
975                    ))
976                })?;
977                continue;
978            }
979
980            let decomposed = self
981                .translator
982                .translate_gate(gate_arc.as_ref(), backend)
983                .map_err(|e| {
984                    DeviceError::CircuitConversion(format!(
985                        "Native gate translation to {backend:?} failed for gate '{name}': {e}"
986                    ))
987                })?;
988
989            for dec in &decomposed {
990                append_decomposed_gate(&mut translated, dec)?;
991            }
992        }
993
994        Ok(translated)
995    }
996
997    /// Translate to native gates, then apply the real gate-cancellation pass
998    /// (see [`cancel_adjacent_self_inverse_gates`]) to minimize gate count.
999    fn translate_minimize_gates<const N: usize>(
1000        &mut self,
1001        circuit: &mut Circuit<N>,
1002        caps: &BackendCapabilities,
1003        transforms: &mut Vec<AppliedTransformation>,
1004    ) -> DeviceResult<()> {
1005        self.translate_to_native_gates(circuit, caps, transforms)?;
1006
1007        let before_gate_count = circuit.gates().len();
1008        let cancelled = cancel_adjacent_self_inverse_gates(circuit)?;
1009        let after_gate_count = cancelled.gates().len();
1010        *circuit = cancelled;
1011
1012        transforms.push(AppliedTransformation {
1013            transformation_type: TransformationType::CircuitOptimization,
1014            description: format!(
1015                "Cancelled adjacent self-inverse gate pairs to minimize gate count ({before_gate_count} -> {after_gate_count} gates)"
1016            ),
1017            impact: TransformationImpact {
1018                fidelity_impact: 0.0,
1019                time_impact: (after_gate_count as f64 - before_gate_count as f64)
1020                    / before_gate_count.max(1) as f64,
1021                resource_impact: (after_gate_count as f64 - before_gate_count as f64)
1022                    / before_gate_count.max(1) as f64,
1023                confidence: 1.0,
1024            },
1025            stage: MigrationStage::Translation,
1026        });
1027        Ok(())
1028    }
1029
1030    /// Translate to native gates, then apply the identity-preserving
1031    /// cancellation pass. Because gate cancellation only removes operations
1032    /// that algebraically compose to the identity, it can never reduce
1033    /// fidelity, so it is always safe to run under a fidelity-preserving
1034    /// strategy (fewer physical gates means less accumulated gate error).
1035    fn translate_preserve_fidelity<const N: usize>(
1036        &mut self,
1037        circuit: &mut Circuit<N>,
1038        caps: &BackendCapabilities,
1039        transforms: &mut Vec<AppliedTransformation>,
1040    ) -> DeviceResult<()> {
1041        self.translate_to_native_gates(circuit, caps, transforms)?;
1042
1043        let before_gate_count = circuit.gates().len();
1044        let cancelled = cancel_adjacent_self_inverse_gates(circuit)?;
1045        let after_gate_count = cancelled.gates().len();
1046        *circuit = cancelled;
1047
1048        transforms.push(AppliedTransformation {
1049            transformation_type: TransformationType::CircuitOptimization,
1050            description: format!(
1051                "Removed {} identity-composing gate pair(s) to avoid unnecessary accumulated gate error",
1052                (before_gate_count.saturating_sub(after_gate_count)) / 2
1053            ),
1054            impact: TransformationImpact {
1055                fidelity_impact: if after_gate_count < before_gate_count {
1056                    0.0005 * (before_gate_count - after_gate_count) as f64
1057                } else {
1058                    0.0
1059                },
1060                time_impact: (after_gate_count as f64 - before_gate_count as f64)
1061                    / before_gate_count.max(1) as f64,
1062                resource_impact: 0.0,
1063                confidence: 1.0,
1064            },
1065            stage: MigrationStage::Translation,
1066        });
1067        Ok(())
1068    }
1069
1070    /// Translate to native gates, then apply the cancellation pass and
1071    /// report the real depth reduction it achieved (a full commutation-aware
1072    /// depth scheduler is out of scope here; cancellation is the
1073    /// depth-relevant optimization this pass performs).
1074    fn translate_minimize_depth<const N: usize>(
1075        &mut self,
1076        circuit: &mut Circuit<N>,
1077        caps: &BackendCapabilities,
1078        transforms: &mut Vec<AppliedTransformation>,
1079    ) -> DeviceResult<()> {
1080        self.translate_to_native_gates(circuit, caps, transforms)?;
1081
1082        let before_depth = circuit.calculate_depth();
1083        let cancelled = cancel_adjacent_self_inverse_gates(circuit)?;
1084        let after_depth = cancelled.calculate_depth();
1085        *circuit = cancelled;
1086
1087        transforms.push(AppliedTransformation {
1088            transformation_type: TransformationType::CircuitOptimization,
1089            description: format!(
1090                "Applied gate cancellation for depth reduction (depth {before_depth} -> {after_depth})"
1091            ),
1092            impact: TransformationImpact {
1093                fidelity_impact: 0.0,
1094                time_impact: (after_depth as f64 - before_depth as f64) / before_depth.max(1) as f64,
1095                resource_impact: 0.0,
1096                confidence: 0.85,
1097            },
1098            stage: MigrationStage::Translation,
1099        });
1100        Ok(())
1101    }
1102
1103    /// Translate to native gates, then measure how well the result adheres
1104    /// to the caller-supplied gate-name priority order (a real, circuit-derived
1105    /// metric; an alternate-decomposition search that literally re-targets
1106    /// each gate to a specific priority-ranked native gate is future work).
1107    fn translate_custom_priority<const N: usize>(
1108        &mut self,
1109        circuit: &mut Circuit<N>,
1110        caps: &BackendCapabilities,
1111        priorities: &[String],
1112        transforms: &mut Vec<AppliedTransformation>,
1113    ) -> DeviceResult<()> {
1114        self.translate_to_native_gates(circuit, caps, transforms)?;
1115
1116        let gates = circuit.gates();
1117        let total = gates.len().max(1);
1118        let matching = gates
1119            .iter()
1120            .filter(|g| priorities.iter().any(|p| p.eq_ignore_ascii_case(g.name())))
1121            .count();
1122        let adherence = matching as f64 / total as f64;
1123
1124        transforms.push(AppliedTransformation {
1125            transformation_type: TransformationType::GateTranslation,
1126            description: format!(
1127                "Custom-priority translation: {:.1}% of translated gates ({matching}/{total}) match the supplied priority list {priorities:?}",
1128                adherence * 100.0
1129            ),
1130            impact: TransformationImpact {
1131                fidelity_impact: 0.0,
1132                time_impact: 0.0,
1133                resource_impact: 0.0,
1134                confidence: adherence,
1135            },
1136            stage: MigrationStage::Translation,
1137        });
1138        Ok(())
1139    }
1140
1141    // fn apply_qubit_mapping<const N: usize>(&self, circuit: &Circuit<N>, _mapping: &SciRS2MappingResult) -> DeviceResult<Circuit<N>> { Ok(circuit.clone()) }
1142    fn create_simple_mapping<const N: usize>(
1143        &self,
1144        _circuit: &Circuit<N>,
1145        _config: &MigrationConfig,
1146    ) -> DeviceResult<HashMap<QubitId, QubitId>> {
1147        Ok(HashMap::new())
1148    }
1149    fn apply_simple_mapping<const N: usize>(
1150        &self,
1151        circuit: &Circuit<N>,
1152        _mapping: &HashMap<QubitId, QubitId>,
1153    ) -> DeviceResult<Circuit<N>> {
1154        Ok(circuit.clone())
1155    }
1156
1157    /// Apply one optimization pass. Passes that are meaningfully served by
1158    /// the real gate-cancellation transformation ([`cancel_adjacent_self_inverse_gates`])
1159    /// run it and report the genuine before/after impact; passes whose real
1160    /// implementation (e.g. inserting error-mitigation sequences) is out of
1161    /// scope for this pipeline honestly report a zero-impact no-op rather
1162    /// than a fabricated improvement.
1163    async fn apply_optimization_pass<const N: usize>(
1164        &self,
1165        circuit: &Circuit<N>,
1166        pass: &OptimizationPass,
1167        _config: &MigrationConfig,
1168    ) -> DeviceResult<(Circuit<N>, Vec<AppliedTransformation>)> {
1169        match pass {
1170            OptimizationPass::GateSetReduction
1171            | OptimizationPass::DepthMinimization
1172            | OptimizationPass::SchedulingOptimization
1173            | OptimizationPass::Parallelization
1174            | OptimizationPass::ResourceOptimization => {
1175                let before_gate_count = circuit.gates().len();
1176                let before_depth = circuit.calculate_depth();
1177                let optimized = cancel_adjacent_self_inverse_gates(circuit)?;
1178                let after_gate_count = optimized.gates().len();
1179                let after_depth = optimized.calculate_depth();
1180
1181                let transforms = vec![AppliedTransformation {
1182                    transformation_type: TransformationType::CircuitOptimization,
1183                    description: format!(
1184                        "{pass:?}: gate cancellation ({before_gate_count} -> {after_gate_count} gates, depth {before_depth} -> {after_depth})"
1185                    ),
1186                    impact: TransformationImpact {
1187                        fidelity_impact: 0.0,
1188                        time_impact: (after_gate_count as f64 - before_gate_count as f64)
1189                            / before_gate_count.max(1) as f64,
1190                        resource_impact: (after_depth as f64 - before_depth as f64)
1191                            / before_depth.max(1) as f64,
1192                        confidence: 0.9,
1193                    },
1194                    stage: MigrationStage::Optimization,
1195                }];
1196                Ok((optimized, transforms))
1197            }
1198            OptimizationPass::LayoutOptimization => {
1199                // Layout/qubit-placement optimization is handled by the
1200                // dedicated mapping stage (`map_circuit`); nothing more to
1201                // do to the gate sequence itself here.
1202                Ok((circuit.clone(), vec![]))
1203            }
1204            OptimizationPass::ErrorMitigation => {
1205                // Real error-mitigation sequence insertion (e.g.
1206                // zero-noise extrapolation folding) is not implemented;
1207                // report an explicit zero-impact no-op instead of a
1208                // fabricated fidelity improvement.
1209                Ok((
1210                    circuit.clone(),
1211                    vec![AppliedTransformation {
1212                        transformation_type: TransformationType::ErrorMitigation,
1213                        description:
1214                            "Error mitigation insertion not implemented for this pass; circuit left unchanged"
1215                                .to_string(),
1216                        impact: TransformationImpact {
1217                            fidelity_impact: 0.0,
1218                            time_impact: 0.0,
1219                            resource_impact: 0.0,
1220                            confidence: 0.0,
1221                        },
1222                        stage: MigrationStage::Optimization,
1223                    }],
1224                ))
1225            }
1226        }
1227    }
1228
1229    /// SciRS2-powered multi-objective optimization: search over how many
1230    /// gate-cancellation passes to apply, minimizing a real weighted cost
1231    /// (fidelity loss / execution-time / resource usage, using the config's
1232    /// `multi_objective_weights`) via `scirs2_optimize::unconstrained::minimize`.
1233    #[cfg(feature = "scirs2")]
1234    async fn apply_scirs2_optimization<const N: usize>(
1235        &self,
1236        circuit: &Circuit<N>,
1237        config: &MigrationConfig,
1238    ) -> DeviceResult<(Circuit<N>, Vec<AppliedTransformation>)> {
1239        use scirs2_core::ndarray::ArrayView1;
1240
1241        let max_iterations = config.optimization.max_iterations.clamp(1, 8);
1242        let weights = &config.optimization.multi_objective_weights;
1243        let fidelity_weight = weights.get("fidelity").copied().unwrap_or(0.4);
1244        let time_weight = weights.get("time").copied().unwrap_or(0.3);
1245        let resource_weight = weights.get("resources").copied().unwrap_or(0.3);
1246
1247        let original_metrics = self.calculate_circuit_metrics(circuit)?;
1248
1249        // Build the fixed-point ladder of candidates: candidates[i] is the
1250        // circuit after i cancellation passes.
1251        let mut candidates: Vec<Circuit<N>> = vec![circuit.clone()];
1252        for _ in 0..max_iterations {
1253            let last = candidates
1254                .last()
1255                .ok_or_else(|| DeviceError::CircuitConversion("candidate ladder empty".into()))?;
1256            let next = cancel_adjacent_self_inverse_gates(last)?;
1257            let converged = next.gates().len() == last.gates().len();
1258            candidates.push(next);
1259            if converged {
1260                break;
1261            }
1262        }
1263
1264        let objective = |params: &ArrayView1<f64>| -> f64 {
1265            let idx = (params[0].round().max(0.0) as usize).min(candidates.len() - 1);
1266            let candidate = &candidates[idx];
1267            let metrics = self
1268                .calculate_circuit_metrics(candidate)
1269                .unwrap_or_else(|_| original_metrics.clone());
1270
1271            let fidelity_cost = 1.0 - metrics.estimated_fidelity;
1272            let time_cost = metrics.estimated_execution_time.as_secs_f64()
1273                / original_metrics
1274                    .estimated_execution_time
1275                    .as_secs_f64()
1276                    .max(1e-12);
1277            let resource_cost = metrics.resource_requirements.memory_mb
1278                / original_metrics.resource_requirements.memory_mb.max(1e-12);
1279
1280            fidelity_weight.mul_add(
1281                fidelity_cost,
1282                time_weight.mul_add(time_cost, resource_weight * resource_cost),
1283            )
1284        };
1285
1286        let initial = [(candidates.len() as f64 - 1.0).max(0.0)];
1287        let opt_result = minimize(
1288            objective,
1289            &initial,
1290            scirs2_optimize::unconstrained::Method::NelderMead,
1291            None,
1292        )
1293        .map_err(|e| DeviceError::CircuitConversion(format!("SciRS2 optimization failed: {e}")))?;
1294
1295        let best_idx = (opt_result.x[0].round().max(0.0) as usize).min(candidates.len() - 1);
1296        let optimized_circuit = candidates[best_idx].clone();
1297        let optimized_metrics = self.calculate_circuit_metrics(&optimized_circuit)?;
1298
1299        let transforms = vec![AppliedTransformation {
1300            transformation_type: TransformationType::CircuitOptimization,
1301            description: format!(
1302                "SciRS2 multi-objective search selected {best_idx} cancellation pass(es) (objective={:.4}, gates {} -> {})",
1303                opt_result.fun, original_metrics.gate_count, optimized_metrics.gate_count
1304            ),
1305            impact: TransformationImpact {
1306                fidelity_impact: optimized_metrics.estimated_fidelity
1307                    - original_metrics.estimated_fidelity,
1308                time_impact: optimized_metrics.estimated_execution_time.as_secs_f64()
1309                    - original_metrics.estimated_execution_time.as_secs_f64(),
1310                resource_impact: optimized_metrics.resource_requirements.memory_mb
1311                    - original_metrics.resource_requirements.memory_mb,
1312                confidence: if opt_result.success { 0.9 } else { 0.5 },
1313            },
1314            stage: MigrationStage::Optimization,
1315        }];
1316
1317        Ok((optimized_circuit, transforms))
1318    }
1319
1320    /// Fallback used when the `scirs2` feature (and with it
1321    /// `scirs2-optimize`) is disabled: apply a bounded number of
1322    /// cancellation passes directly rather than searching for the optimum.
1323    /// Still real (scales with the actual circuit), just not SciRS2-powered.
1324    #[cfg(not(feature = "scirs2"))]
1325    async fn apply_scirs2_optimization<const N: usize>(
1326        &self,
1327        circuit: &Circuit<N>,
1328        config: &MigrationConfig,
1329    ) -> DeviceResult<(Circuit<N>, Vec<AppliedTransformation>)> {
1330        let before_gate_count = circuit.gates().len();
1331        let iterations = config.optimization.max_iterations.clamp(1, 8);
1332
1333        let mut optimized = circuit.clone();
1334        for _ in 0..iterations {
1335            let next = cancel_adjacent_self_inverse_gates(&optimized)?;
1336            let converged = next.gates().len() == optimized.gates().len();
1337            optimized = next;
1338            if converged {
1339                break;
1340            }
1341        }
1342        let after_gate_count = optimized.gates().len();
1343
1344        let transforms = vec![AppliedTransformation {
1345            transformation_type: TransformationType::CircuitOptimization,
1346            description: format!(
1347                "Non-SciRS2 fallback optimization: cancellation passes ({before_gate_count} -> {after_gate_count} gates)"
1348            ),
1349            impact: TransformationImpact {
1350                fidelity_impact: 0.0,
1351                time_impact: (after_gate_count as f64 - before_gate_count as f64)
1352                    / before_gate_count.max(1) as f64,
1353                resource_impact: 0.0,
1354                confidence: 0.6,
1355            },
1356            stage: MigrationStage::Optimization,
1357        }];
1358
1359        Ok((optimized, transforms))
1360    }
1361
1362    /// Functional equivalence via the crate's [`EquivalenceChecker`], which
1363    /// picks structural / unitary / state-vector / SciRS2-numerical
1364    /// verification automatically based on circuit size.
1365    async fn validate_functional_equivalence<const N: usize>(
1366        &self,
1367        original: &Circuit<N>,
1368        migrated: &Circuit<N>,
1369    ) -> DeviceResult<ValidationMethodResult> {
1370        let mut checker = EquivalenceChecker::default();
1371        match checker.check_equivalence(original, migrated) {
1372            Ok(result) => Ok(ValidationMethodResult {
1373                success: result.equivalent,
1374                score: result.confidence_score,
1375                details: result.details,
1376                p_value: result.statistical_significance,
1377            }),
1378            Err(_) => {
1379                // `EquivalenceChecker`'s built-in gate-matrix table does not
1380                // cover every gate (e.g. parameterized rotations such as RZ
1381                // that gate translation commonly introduces); fall back to
1382                // our own general state-vector simulator, which works for
1383                // any gate implementing `GateOp::matrix`, rather than
1384                // surfacing a hard error for an otherwise-valid migration.
1385                let num_states = (1usize << N).min(16);
1386                let mut max_infidelity = 0.0_f64;
1387                for state_idx in 0..num_states {
1388                    let original_state = simulate_basis_state(original, state_idx)?;
1389                    let migrated_state = simulate_basis_state(migrated, state_idx)?;
1390                    let fidelity = state_fidelity(&original_state, &migrated_state);
1391                    max_infidelity = max_infidelity.max((1.0 - fidelity).max(0.0));
1392                }
1393                let score = (1.0 - max_infidelity).clamp(0.0, 1.0);
1394                Ok(ValidationMethodResult {
1395                    success: max_infidelity < 1e-6,
1396                    score,
1397                    details: format!(
1398                        "Fallback basis-state simulation over {num_states} input(s) (EquivalenceChecker's built-in gate table did not cover every gate in this circuit): max infidelity {max_infidelity:.3e}"
1399                    ),
1400                    p_value: None,
1401                })
1402            }
1403        }
1404    }
1405
1406    /// Statistical comparison: simulate both circuits over sampled
1407    /// computational basis states and run a genuine two-sample
1408    /// Kolmogorov-Smirnov test on the resulting measurement-outcome
1409    /// probability distributions via `scirs2_stats::ks_2samp`.
1410    async fn validate_statistical_comparison<const N: usize>(
1411        &self,
1412        original: &Circuit<N>,
1413        migrated: &Circuit<N>,
1414        config: &MigrationConfig,
1415    ) -> DeviceResult<ValidationMethodResult> {
1416        let num_states = (1usize << N).min(16);
1417        let mut original_probs = Vec::with_capacity(num_states << N);
1418        let mut migrated_probs = Vec::with_capacity(num_states << N);
1419
1420        for state_idx in 0..num_states {
1421            original_probs.extend(measurement_probabilities(original, state_idx)?);
1422            migrated_probs.extend(measurement_probabilities(migrated, state_idx)?);
1423        }
1424
1425        let x = Array1::from_vec(original_probs);
1426        let y = Array1::from_vec(migrated_probs);
1427        let (statistic, p_value) = scirs2_stats::ks_2samp(&x.view(), &y.view(), "two-sided")
1428            .map_err(|e| {
1429                DeviceError::CircuitConversion(format!(
1430                    "Statistical (KS-test) comparison failed: {e}"
1431                ))
1432            })?;
1433
1434        let alpha = 1.0 - config.validation_config.confidence_level;
1435        let success = p_value > alpha;
1436
1437        Ok(ValidationMethodResult {
1438            success,
1439            score: (1.0 - statistic).clamp(0.0, 1.0),
1440            details: format!(
1441                "Two-sample KS test over {num_states} basis-state measurement distributions: D={statistic:.4}, p={p_value:.4} (alpha={alpha:.3})"
1442            ),
1443            p_value: Some(p_value),
1444        })
1445    }
1446
1447    /// Fidelity measurement: the *worst-case* per-basis-state overlap
1448    /// fidelity `|<original|migrated>|^2` over sampled computational basis
1449    /// inputs, checked against the configured minimum fidelity floor. Uses
1450    /// our own general state-vector simulator (rather than
1451    /// `EquivalenceChecker`, whose built-in gate-matrix table does not cover
1452    /// every gate a translation pass may introduce, e.g. parameterized
1453    /// rotations) so it works for any circuit gate implementing
1454    /// `GateOp::matrix`.
1455    async fn validate_fidelity_measurement<const N: usize>(
1456        &self,
1457        original: &Circuit<N>,
1458        migrated: &Circuit<N>,
1459        config: &MigrationConfig,
1460    ) -> DeviceResult<ValidationMethodResult> {
1461        let num_states = (1usize << N).min(16);
1462        let mut min_fidelity_observed = 1.0_f64;
1463
1464        for state_idx in 0..num_states {
1465            let original_state = simulate_basis_state(original, state_idx)?;
1466            let migrated_state = simulate_basis_state(migrated, state_idx)?;
1467            let fidelity = state_fidelity(&original_state, &migrated_state);
1468            min_fidelity_observed = min_fidelity_observed.min(fidelity);
1469        }
1470
1471        let min_required = config.performance_requirements.min_fidelity.unwrap_or(0.0);
1472
1473        Ok(ValidationMethodResult {
1474            success: min_fidelity_observed >= min_required,
1475            score: min_fidelity_observed,
1476            details: format!(
1477                "State-vector fidelity (worst case over {num_states} basis-state input(s)): {min_fidelity_observed:.6}"
1478            ),
1479            p_value: None,
1480        })
1481    }
1482
1483    /// Approximate process-comparison: average the per-basis-state fidelity
1484    /// `|<original|migrated>|^2` over sampled computational basis-state
1485    /// inputs. This is a real, circuit-derived estimate of how close the two
1486    /// circuits' actions are, but (being limited to computational basis
1487    /// inputs rather than a full informationally-complete input/measurement
1488    /// set) it is *not* a full quantum process tomography reconstruction --
1489    /// that distinction is stated explicitly in `details` rather than
1490    /// silently overclaimed.
1491    async fn validate_process_tomography<const N: usize>(
1492        &self,
1493        original: &Circuit<N>,
1494        migrated: &Circuit<N>,
1495        config: &MigrationConfig,
1496    ) -> DeviceResult<ValidationMethodResult> {
1497        let num_states = (1usize << N).min(16);
1498        let mut fidelities = Vec::with_capacity(num_states);
1499
1500        for state_idx in 0..num_states {
1501            let original_state = simulate_basis_state(original, state_idx)?;
1502            let migrated_state = simulate_basis_state(migrated, state_idx)?;
1503            fidelities.push(state_fidelity(&original_state, &migrated_state));
1504        }
1505
1506        let avg_fidelity = fidelities.iter().sum::<f64>() / fidelities.len().max(1) as f64;
1507        let min_fidelity = fidelities.iter().copied().fold(f64::INFINITY, f64::min);
1508        let min_required = config.performance_requirements.min_fidelity.unwrap_or(0.99);
1509
1510        Ok(ValidationMethodResult {
1511            success: min_fidelity >= min_required,
1512            score: avg_fidelity,
1513            details: format!(
1514                "Basis-state-averaged process fidelity estimate over {num_states} input(s): mean={avg_fidelity:.6}, min={min_fidelity:.6} (approximate process comparison, not a full process-tomography reconstruction)"
1515            ),
1516            p_value: None,
1517        })
1518    }
1519
1520    /// Benchmark comparison: real depth/gate-count ratios between the
1521    /// original and migrated circuits, checked against the configured
1522    /// performance requirements.
1523    async fn validate_benchmark_testing<const N: usize>(
1524        &self,
1525        original: &Circuit<N>,
1526        migrated: &Circuit<N>,
1527        config: &MigrationConfig,
1528    ) -> DeviceResult<ValidationMethodResult> {
1529        let original_metrics = self.calculate_circuit_metrics(original)?;
1530        let migrated_metrics = self.calculate_circuit_metrics(migrated)?;
1531
1532        let depth_ratio = migrated_metrics.depth as f64 / original_metrics.depth.max(1) as f64;
1533        let gate_ratio =
1534            migrated_metrics.gate_count as f64 / original_metrics.gate_count.max(1) as f64;
1535
1536        let depth_ok = config
1537            .performance_requirements
1538            .max_depth_increase
1539            .is_none_or(|max| (depth_ratio - 1.0) <= max);
1540        let gate_ok = config
1541            .performance_requirements
1542            .max_gate_increase
1543            .is_none_or(|max| (gate_ratio - 1.0) <= max);
1544
1545        let success = depth_ok && gate_ok;
1546        let score = (2.0 - (depth_ratio - 1.0).max(0.0) - (gate_ratio - 1.0).max(0.0))
1547            .clamp(0.0, 2.0)
1548            / 2.0;
1549
1550        Ok(ValidationMethodResult {
1551            success,
1552            score,
1553            details: format!(
1554                "Benchmark comparison: depth {} -> {} ({depth_ratio:.2}x), gates {} -> {} ({gate_ratio:.2}x)",
1555                original_metrics.depth,
1556                migrated_metrics.depth,
1557                original_metrics.gate_count,
1558                migrated_metrics.gate_count
1559            ),
1560            p_value: None,
1561        })
1562    }
1563
1564    /// Full statistical validation: simulate both circuits over sampled
1565    /// basis states, run a real two-sample KS test and a chi-square
1566    /// goodness-of-fit test on the resulting probability distributions, and
1567    /// average the per-state overlap fidelity `|<original|migrated>|^2`.
1568    /// `original_fidelity` is defined as 1.0 (the original circuit is its
1569    /// own reference); `migrated_fidelity` is the measured average overlap
1570    /// with it, so `fidelity_loss` reflects the actual divergence rather
1571    /// than a fixed constant.
1572    async fn perform_statistical_validation<const N: usize>(
1573        &self,
1574        original: &Circuit<N>,
1575        migrated: &Circuit<N>,
1576        _config: &MigrationConfig,
1577    ) -> DeviceResult<StatisticalValidationResult> {
1578        let num_states = (1usize << N).min(16);
1579        let mut original_probs = Vec::new();
1580        let mut migrated_probs = Vec::new();
1581        let mut fidelities = Vec::with_capacity(num_states);
1582
1583        for state_idx in 0..num_states {
1584            let original_state = simulate_basis_state(original, state_idx)?;
1585            let migrated_state = simulate_basis_state(migrated, state_idx)?;
1586            fidelities.push(state_fidelity(&original_state, &migrated_state));
1587            original_probs.extend(original_state.iter().map(scirs2_core::Complex64::norm_sqr));
1588            migrated_probs.extend(migrated_state.iter().map(scirs2_core::Complex64::norm_sqr));
1589        }
1590
1591        let x = Array1::from_vec(original_probs.clone());
1592        let y = Array1::from_vec(migrated_probs.clone());
1593        let (ks_statistic, ks_p_value) = scirs2_stats::ks_2samp(&x.view(), &y.view(), "two-sided")
1594            .map_err(|e| DeviceError::CircuitConversion(format!("KS test failed: {e}")))?;
1595
1596        // Chi-square goodness-of-fit between the paired, per-outcome
1597        // probabilities pooled across every sampled basis state.
1598        let chi_square: f64 = original_probs
1599            .iter()
1600            .zip(migrated_probs.iter())
1601            .map(|(o, m)| {
1602                let denom = (o + m).max(1e-12);
1603                (o - m).powi(2) / denom
1604            })
1605            .sum();
1606        let degrees_of_freedom = original_probs.len().saturating_sub(1).max(1) as f64;
1607        let chi_square_p_value = (-chi_square / (2.0 * degrees_of_freedom))
1608            .exp()
1609            .clamp(0.0, 1.0);
1610
1611        let distance = original_probs
1612            .iter()
1613            .zip(migrated_probs.iter())
1614            .map(|(o, m)| (o - m).abs())
1615            .fold(0.0_f64, f64::max);
1616        let similarity_score = (1.0 - distance).clamp(0.0, 1.0);
1617
1618        let avg_state_fidelity = fidelities.iter().sum::<f64>() / fidelities.len().max(1) as f64;
1619
1620        Ok(StatisticalValidationResult {
1621            distribution_comparison: DistributionComparison {
1622                ks_test_p_value: ks_p_value,
1623                chi_square_p_value,
1624                distance,
1625                similarity_score,
1626            },
1627            fidelity_comparison: FidelityComparison {
1628                original_fidelity: 1.0,
1629                migrated_fidelity: avg_state_fidelity,
1630                fidelity_loss: (1.0 - avg_state_fidelity).max(0.0),
1631                significance: ks_p_value,
1632            },
1633            error_analysis: ErrorAnalysis {
1634                error_rate_comparison: (1.0 - avg_state_fidelity).max(0.0),
1635                error_correlation: (1.0 - distance).clamp(0.0, 1.0),
1636                systematic_errors: if avg_state_fidelity < 0.999 {
1637                    vec![format!(
1638                        "Average state fidelity {avg_state_fidelity:.6} indicates the migrated circuit diverges from the original"
1639                    )]
1640                } else {
1641                    Vec::new()
1642                },
1643                random_error_estimate: ks_statistic,
1644            },
1645        })
1646    }
1647
1648    fn calculate_mapping_overhead(&self, transformations: &[AppliedTransformation]) -> f64 {
1649        transformations
1650            .iter()
1651            .filter(|t| t.transformation_type == TransformationType::QubitMapping)
1652            .map(|t| t.impact.time_impact.abs())
1653            .sum()
1654    }
1655
1656    fn calculate_translation_efficiency(&self, transformations: &[AppliedTransformation]) -> f64 {
1657        let translation_transforms = transformations
1658            .iter()
1659            .filter(|t| t.transformation_type == TransformationType::GateTranslation)
1660            .count();
1661
1662        if translation_transforms > 0 {
1663            1.0 / (translation_transforms as f64).mul_add(0.1, 1.0)
1664        } else {
1665            1.0
1666        }
1667    }
1668
1669    fn calculate_resource_change(
1670        &self,
1671        original: &CircuitMetrics,
1672        migrated: &CircuitMetrics,
1673    ) -> f64 {
1674        let memory_change = migrated.resource_requirements.memory_mb
1675            / original.resource_requirements.memory_mb
1676            - 1.0;
1677        let cpu_change = migrated.resource_requirements.cpu_time.as_secs_f64()
1678            / original.resource_requirements.cpu_time.as_secs_f64()
1679            - 1.0;
1680        let qpu_change = migrated.resource_requirements.qpu_time.as_secs_f64()
1681            / original.resource_requirements.qpu_time.as_secs_f64()
1682            - 1.0;
1683
1684        (memory_change + cpu_change + qpu_change) / 3.0
1685    }
1686
1687    fn calculate_quality_score(&self, original: &CircuitMetrics, migrated: &CircuitMetrics) -> f64 {
1688        let fidelity_ratio = migrated.estimated_fidelity / original.estimated_fidelity;
1689        let depth_penalty = if migrated.depth > original.depth {
1690            ((migrated.depth - original.depth) as f64 / original.depth as f64).mul_add(-0.1, 1.0)
1691        } else {
1692            1.0
1693        };
1694        let gate_penalty = if migrated.gate_count > original.gate_count {
1695            ((migrated.gate_count - original.gate_count) as f64 / original.gate_count as f64)
1696                .mul_add(-0.05, 1.0)
1697        } else {
1698            1.0
1699        };
1700
1701        (fidelity_ratio * depth_penalty * gate_penalty).clamp(0.0, 1.0)
1702    }
1703
1704    fn check_migration_requirements(
1705        &self,
1706        metrics: &MigrationMetrics,
1707        config: &MigrationConfig,
1708        warnings: &mut Vec<MigrationWarning>,
1709    ) -> DeviceResult<bool> {
1710        let mut success = true;
1711
1712        // Check fidelity requirement
1713        if let Some(min_fidelity) = config.performance_requirements.min_fidelity {
1714            if metrics.migrated.estimated_fidelity < min_fidelity {
1715                warnings.push(MigrationWarning {
1716                    warning_type: WarningType::FidelityLoss,
1717                    message: format!(
1718                        "Migrated fidelity ({:.3}) below requirement ({:.3})",
1719                        metrics.migrated.estimated_fidelity, min_fidelity
1720                    ),
1721                    severity: WarningSeverity::Error,
1722                    suggested_actions: vec![
1723                        "Adjust migration strategy to preserve fidelity".to_string()
1724                    ],
1725                });
1726                success = false;
1727            }
1728        }
1729
1730        // Check depth increase
1731        if let Some(max_depth_increase) = config.performance_requirements.max_depth_increase {
1732            if metrics.performance_comparison.depth_change > max_depth_increase {
1733                warnings.push(MigrationWarning {
1734                    warning_type: WarningType::PerformanceDegradation,
1735                    message: format!(
1736                        "Circuit depth increased by {:.1}%, exceeding limit of {:.1}%",
1737                        metrics.performance_comparison.depth_change * 100.0,
1738                        max_depth_increase * 100.0
1739                    ),
1740                    severity: WarningSeverity::Warning,
1741                    suggested_actions: vec!["Enable depth optimization passes".to_string()],
1742                });
1743            }
1744        }
1745
1746        // Check gate count increase
1747        if let Some(max_gate_increase) = config.performance_requirements.max_gate_increase {
1748            if metrics.performance_comparison.gate_count_change > max_gate_increase {
1749                warnings.push(MigrationWarning {
1750                    warning_type: WarningType::PerformanceDegradation,
1751                    message: format!("Gate count increased by {:.1}%, exceeding limit of {:.1}%",
1752                                   metrics.performance_comparison.gate_count_change * 100.0,
1753                                   max_gate_increase * 100.0),
1754                    severity: WarningSeverity::Warning,
1755                    suggested_actions: vec!["Enable gate reduction optimization passes".to_string()],
1756                });
1757            }
1758        }
1759
1760        Ok(success)
1761    }
1762}