use quantrs2_circuit::prelude::*;
use quantrs2_core::{
error::{QuantRS2Error, QuantRS2Result},
gate::GateOp,
qubit::QubitId,
};
use scirs2_core::ndarray::{Array1, Array2, Array3, Array4, ArrayView1, ArrayView2};
use scirs2_core::Complex64;
use std::collections::HashMap;
use crate::{
backend_traits::{query_backend_capabilities, BackendCapabilities},
calibration::{CalibrationManager, DeviceCalibration},
characterization::{ProcessTomography, StateTomography},
noise_model::CalibrationNoiseModel,
translation::HardwareBackend,
CircuitResult, DeviceError, DeviceResult,
};
use super::{
analysis::*,
config::{ReconstructionMethod, SciRS2ProcessTomographyConfig},
reconstruction::*,
results::*,
utils::*,
validation::*,
};
#[cfg(feature = "scirs2")]
use scirs2_graph::{
betweenness_centrality, closeness_centrality, dijkstra_path, minimum_spanning_tree,
strongly_connected_components, Graph,
};
#[cfg(feature = "scirs2")]
use scirs2_linalg::{
cholesky, det, eigvals, inv, matrix_norm, prelude::*, qr, svd, trace, LinalgError, LinalgResult,
};
#[cfg(feature = "scirs2")]
use scirs2_optimize::{minimize, OptimizeResult};
#[cfg(feature = "scirs2")]
use scirs2_stats::{
corrcoef,
distributions::{chi2, gamma, norm},
ks_2samp, mean, pearsonr, shapiro_wilk, spearmanr, std, ttest_1samp, ttest_ind, var,
Alternative, TTestResult,
};
#[cfg(not(feature = "scirs2"))]
use super::fallback::*;
pub struct SciRS2ProcessTomographer {
pub(crate) config: SciRS2ProcessTomographyConfig,
pub(crate) calibration_manager: CalibrationManager,
pub(crate) input_states: Vec<Array2<Complex64>>,
pub(crate) measurement_operators: Vec<Array2<Complex64>>,
}
impl SciRS2ProcessTomographer {
pub const fn new(
config: SciRS2ProcessTomographyConfig,
calibration_manager: CalibrationManager,
) -> Self {
Self {
config,
calibration_manager,
input_states: Vec::new(),
measurement_operators: Vec::new(),
}
}
pub fn generate_input_states(&mut self, num_qubits: usize) -> DeviceResult<()> {
self.input_states = self.create_informationally_complete_states(num_qubits)?;
Ok(())
}
pub fn generate_measurement_operators(&mut self, num_qubits: usize) -> DeviceResult<()> {
self.measurement_operators = self.create_pauli_measurements(num_qubits)?;
Ok(())
}
pub async fn perform_process_tomography<const N: usize, E: ProcessTomographyExecutor>(
&self,
device_id: &str,
process_circuit: &Circuit<N>,
executor: &E,
) -> DeviceResult<SciRS2ProcessTomographyResult> {
let experimental_data = self
.collect_experimental_data(process_circuit, executor)
.await?;
let (process_matrix, reconstruction_quality) = match self.config.reconstruction_method {
ReconstructionMethod::LinearInversion => {
self.linear_inversion_reconstruction(&experimental_data)?
}
ReconstructionMethod::MaximumLikelihood => {
self.maximum_likelihood_reconstruction(&experimental_data)?
}
ReconstructionMethod::CompressedSensing => {
self.compressed_sensing_reconstruction(&experimental_data)?
}
ReconstructionMethod::BayesianInference => {
self.bayesian_reconstruction(&experimental_data)?
}
ReconstructionMethod::EnsembleMethods => {
self.ensemble_reconstruction(&experimental_data)?
}
ReconstructionMethod::MachineLearning => self.ml_reconstruction(&experimental_data)?,
};
let pauli_transfer_matrix = self.convert_to_pauli_transfer(&process_matrix)?;
let statistical_analysis =
self.perform_statistical_analysis(&process_matrix, &experimental_data)?;
let process_metrics = self.calculate_process_metrics(&process_matrix)?;
let validation_results = if self.config.validation_config.enable_cross_validation {
self.perform_validation(&experimental_data)?
} else {
ProcessValidationResults {
cross_validation: None,
bootstrap_results: None,
benchmark_results: None,
model_selection: ModelSelectionResults {
aic_scores: HashMap::new(),
bic_scores: HashMap::new(),
cross_validation_scores: HashMap::new(),
best_model: "mle".to_string(),
model_weights: HashMap::new(),
},
}
};
let structure_analysis = if self.config.enable_structure_analysis {
Some(self.analyze_process_structure(&process_matrix)?)
} else {
None
};
let uncertainty_quantification =
self.quantify_uncertainty(&process_matrix, &experimental_data)?;
let process_comparisons = self.compare_with_known_processes(&process_matrix)?;
Ok(SciRS2ProcessTomographyResult {
device_id: device_id.to_string(),
config: self.config.clone(),
process_matrix,
pauli_transfer_matrix,
statistical_analysis: ProcessStatisticalAnalysis {
reconstruction_quality,
statistical_tests: statistical_analysis.0,
distribution_analysis: statistical_analysis.1,
correlation_analysis: statistical_analysis.2,
},
process_metrics,
validation_results,
structure_analysis,
uncertainty_quantification,
process_comparisons,
})
}
pub(crate) fn create_informationally_complete_states(
&self,
num_qubits: usize,
) -> DeviceResult<Vec<Array2<Complex64>>> {
let mut states = Vec::new();
let dim = 1 << num_qubits;
if num_qubits == 1 {
let mut state0 = Array2::zeros((2, 2));
state0[[0, 0]] = Complex64::new(1.0, 0.0);
states.push(state0);
let mut state1 = Array2::zeros((2, 2));
state1[[1, 1]] = Complex64::new(1.0, 0.0);
states.push(state1);
let mut state_plus = Array2::zeros((2, 2));
state_plus[[0, 0]] = Complex64::new(0.5, 0.0);
state_plus[[0, 1]] = Complex64::new(0.5, 0.0);
state_plus[[1, 0]] = Complex64::new(0.5, 0.0);
state_plus[[1, 1]] = Complex64::new(0.5, 0.0);
states.push(state_plus);
let mut state_minus = Array2::zeros((2, 2));
state_minus[[0, 0]] = Complex64::new(0.5, 0.0);
state_minus[[0, 1]] = Complex64::new(-0.5, 0.0);
state_minus[[1, 0]] = Complex64::new(-0.5, 0.0);
state_minus[[1, 1]] = Complex64::new(0.5, 0.0);
states.push(state_minus);
let mut state_plus_i = Array2::zeros((2, 2));
state_plus_i[[0, 0]] = Complex64::new(0.5, 0.0);
state_plus_i[[0, 1]] = Complex64::new(0.0, 0.5);
state_plus_i[[1, 0]] = Complex64::new(0.0, -0.5);
state_plus_i[[1, 1]] = Complex64::new(0.5, 0.0);
states.push(state_plus_i);
let mut state_minus_i = Array2::zeros((2, 2));
state_minus_i[[0, 0]] = Complex64::new(0.5, 0.0);
state_minus_i[[0, 1]] = Complex64::new(0.0, -0.5);
state_minus_i[[1, 0]] = Complex64::new(0.0, 0.5);
state_minus_i[[1, 1]] = Complex64::new(0.5, 0.0);
states.push(state_minus_i);
} else {
let single_qubit_states = self.create_informationally_complete_states(1)?;
for combination in self.generate_state_combinations(&single_qubit_states, num_qubits)? {
states.push(combination);
}
}
Ok(states)
}
pub(crate) fn create_pauli_measurements(
&self,
num_qubits: usize,
) -> DeviceResult<Vec<Array2<Complex64>>> {
let mut measurements = Vec::new();
let dim = 1 << num_qubits;
let pauli_i = Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(1.0, 0.0),
],
)
.map_err(|e| DeviceError::APIError(format!("Array creation error: {e}")))?;
let pauli_x = Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(0.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
],
)
.map_err(|e| DeviceError::APIError(format!("Array creation error: {e}")))?;
let pauli_y = Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(0.0, 0.0),
Complex64::new(0.0, -1.0),
Complex64::new(0.0, 1.0),
Complex64::new(0.0, 0.0),
],
)
.map_err(|e| DeviceError::APIError(format!("Array creation error: {e}")))?;
let pauli_z = Array2::from_shape_vec(
(2, 2),
vec![
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(0.0, 0.0),
Complex64::new(-1.0, 0.0),
],
)
.map_err(|e| DeviceError::APIError(format!("Array creation error: {e}")))?;
let single_qubit_paulis = vec![pauli_i, pauli_x, pauli_y, pauli_z];
if num_qubits == 1 {
measurements = single_qubit_paulis;
} else {
measurements = self.generate_pauli_tensor_products(&single_qubit_paulis, num_qubits)?;
}
Ok(measurements)
}
pub fn calculate_process_fidelity(
&self,
process_matrix: &Array4<Complex64>,
) -> DeviceResult<f64> {
let choi_matrix = self.convert_to_choi_matrix(process_matrix)?;
let ideal_choi = self.create_ideal_identity_choi(choi_matrix.dim().0)?;
#[cfg(feature = "scirs2")]
{
let fidelity = self.calculate_choi_fidelity(&choi_matrix, &ideal_choi)?;
Ok(fidelity)
}
#[cfg(not(feature = "scirs2"))]
{
Ok(0.95) }
}
pub fn calculate_entangling_power(
&self,
process_matrix: &Array4<Complex64>,
) -> DeviceResult<f64> {
let dim = process_matrix.dim().0;
if dim <= 2 {
return Ok(0.0);
}
#[cfg(feature = "scirs2")]
{
let choi_matrix = self.convert_to_choi_matrix(process_matrix)?;
let partial_transpose = self.partial_transpose(&choi_matrix)?;
if let Ok(eigenvalues) = eigvals(&partial_transpose.view(), None) {
let negative_eigenvalues: Vec<f64> = eigenvalues
.iter()
.map(|x| x.re)
.filter(|&x| x < 0.0)
.collect();
let entangling_power = negative_eigenvalues.iter().map(|x| x.abs()).sum::<f64>();
Ok(entangling_power)
} else {
Ok(0.0)
}
}
#[cfg(not(feature = "scirs2"))]
{
let mut entangling_power = 0.0;
for i in 0..dim {
for j in 0..dim {
for k in 0..dim {
for l in 0..dim {
if i != j || k != l {
entangling_power += process_matrix[[i, j, k, l]].norm_sqr();
}
}
}
}
}
Ok(entangling_power / (dim * dim) as f64)
}
}
}
pub trait ProcessTomographyExecutor {
fn execute_process_measurement<const N: usize>(
&self,
circuit: &Circuit<N>,
input_state: &Array2<Complex64>,
measurement: &Array2<Complex64>,
shots: usize,
) -> impl std::future::Future<Output = DeviceResult<f64>> + Send;
}