1use quantrs2_circuit::prelude::*;
4use quantrs2_core::{
5 error::{QuantRS2Error, QuantRS2Result},
6 gate::GateOp,
7 qubit::QubitId,
8};
9use scirs2_core::ndarray::{Array1, Array2, Array3, Array4, ArrayView1, ArrayView2};
10use scirs2_core::Complex64;
11use std::collections::HashMap;
12
13use crate::{
14 backend_traits::{query_backend_capabilities, BackendCapabilities},
15 calibration::{CalibrationManager, DeviceCalibration},
16 characterization::{ProcessTomography, StateTomography},
17 noise_model::CalibrationNoiseModel,
18 translation::HardwareBackend,
19 CircuitResult, DeviceError, DeviceResult,
20};
21
22use super::{
23 analysis::*,
24 config::{ReconstructionMethod, SciRS2ProcessTomographyConfig},
25 reconstruction::*,
26 results::*,
27 utils::*,
28 validation::*,
29};
30
31#[cfg(feature = "scirs2")]
33use scirs2_graph::{
34 betweenness_centrality, closeness_centrality, dijkstra_path, minimum_spanning_tree,
35 strongly_connected_components, Graph,
36};
37#[cfg(feature = "scirs2")]
38use scirs2_linalg::{
39 cholesky, det, eigvals, inv, matrix_norm, prelude::*, qr, svd, trace, LinalgError, LinalgResult,
40};
41#[cfg(feature = "scirs2")]
42use scirs2_optimize::{minimize, OptimizeResult};
43#[cfg(feature = "scirs2")]
44use scirs2_stats::{
45 corrcoef,
46 distributions::{chi2, gamma, norm},
47 ks_2samp, mean, pearsonr, shapiro_wilk, spearmanr, std, ttest_1samp, ttest_ind, var,
48 Alternative, TTestResult,
49};
50
51#[cfg(not(feature = "scirs2"))]
53use super::fallback::*;
54
55pub struct SciRS2ProcessTomographer {
57 pub(crate) config: SciRS2ProcessTomographyConfig,
58 pub(crate) calibration_manager: CalibrationManager,
59 pub(crate) input_states: Vec<Array2<Complex64>>,
60 pub(crate) measurement_operators: Vec<Array2<Complex64>>,
61}
62
63impl SciRS2ProcessTomographer {
64 pub const fn new(
66 config: SciRS2ProcessTomographyConfig,
67 calibration_manager: CalibrationManager,
68 ) -> Self {
69 Self {
70 config,
71 calibration_manager,
72 input_states: Vec::new(),
73 measurement_operators: Vec::new(),
74 }
75 }
76
77 pub fn generate_input_states(&mut self, num_qubits: usize) -> DeviceResult<()> {
79 self.input_states = self.create_informationally_complete_states(num_qubits)?;
80 Ok(())
81 }
82
83 pub fn generate_measurement_operators(&mut self, num_qubits: usize) -> DeviceResult<()> {
85 self.measurement_operators = self.create_pauli_measurements(num_qubits)?;
86 Ok(())
87 }
88
89 pub async fn perform_process_tomography<const N: usize, E: ProcessTomographyExecutor>(
91 &self,
92 device_id: &str,
93 process_circuit: &Circuit<N>,
94 executor: &E,
95 ) -> DeviceResult<SciRS2ProcessTomographyResult> {
96 let experimental_data = self
98 .collect_experimental_data(process_circuit, executor)
99 .await?;
100
101 let (process_matrix, reconstruction_quality) = match self.config.reconstruction_method {
103 ReconstructionMethod::LinearInversion => {
104 self.linear_inversion_reconstruction(&experimental_data)?
105 }
106 ReconstructionMethod::MaximumLikelihood => {
107 self.maximum_likelihood_reconstruction(&experimental_data)?
108 }
109 ReconstructionMethod::CompressedSensing => {
110 self.compressed_sensing_reconstruction(&experimental_data)?
111 }
112 ReconstructionMethod::BayesianInference => {
113 self.bayesian_reconstruction(&experimental_data)?
114 }
115 ReconstructionMethod::EnsembleMethods => {
116 self.ensemble_reconstruction(&experimental_data)?
117 }
118 ReconstructionMethod::MachineLearning => self.ml_reconstruction(&experimental_data)?,
119 };
120
121 let pauli_transfer_matrix = self.convert_to_pauli_transfer(&process_matrix)?;
123
124 let statistical_analysis =
126 self.perform_statistical_analysis(&process_matrix, &experimental_data)?;
127
128 let process_metrics = self.calculate_process_metrics(&process_matrix)?;
130
131 let validation_results = if self.config.validation_config.enable_cross_validation {
133 self.perform_validation(&experimental_data)?
134 } else {
135 ProcessValidationResults {
136 cross_validation: None,
137 bootstrap_results: None,
138 benchmark_results: None,
139 model_selection: ModelSelectionResults {
140 aic_scores: HashMap::new(),
141 bic_scores: HashMap::new(),
142 cross_validation_scores: HashMap::new(),
143 best_model: "mle".to_string(),
144 model_weights: HashMap::new(),
145 },
146 }
147 };
148
149 let structure_analysis = if self.config.enable_structure_analysis {
151 Some(self.analyze_process_structure(&process_matrix)?)
152 } else {
153 None
154 };
155
156 let uncertainty_quantification =
158 self.quantify_uncertainty(&process_matrix, &experimental_data)?;
159
160 let process_comparisons = self.compare_with_known_processes(&process_matrix)?;
162
163 Ok(SciRS2ProcessTomographyResult {
164 device_id: device_id.to_string(),
165 config: self.config.clone(),
166 process_matrix,
167 pauli_transfer_matrix,
168 statistical_analysis: ProcessStatisticalAnalysis {
169 reconstruction_quality,
170 statistical_tests: statistical_analysis.0,
171 distribution_analysis: statistical_analysis.1,
172 correlation_analysis: statistical_analysis.2,
173 },
174 process_metrics,
175 validation_results,
176 structure_analysis,
177 uncertainty_quantification,
178 process_comparisons,
179 })
180 }
181
182 pub(crate) fn create_informationally_complete_states(
184 &self,
185 num_qubits: usize,
186 ) -> DeviceResult<Vec<Array2<Complex64>>> {
187 let mut states = Vec::new();
188 let dim = 1 << num_qubits; if num_qubits == 1 {
193 let mut state0 = Array2::zeros((2, 2));
195 state0[[0, 0]] = Complex64::new(1.0, 0.0);
196 states.push(state0);
197
198 let mut state1 = Array2::zeros((2, 2));
200 state1[[1, 1]] = Complex64::new(1.0, 0.0);
201 states.push(state1);
202
203 let mut state_plus = Array2::zeros((2, 2));
205 state_plus[[0, 0]] = Complex64::new(0.5, 0.0);
206 state_plus[[0, 1]] = Complex64::new(0.5, 0.0);
207 state_plus[[1, 0]] = Complex64::new(0.5, 0.0);
208 state_plus[[1, 1]] = Complex64::new(0.5, 0.0);
209 states.push(state_plus);
210
211 let mut state_minus = Array2::zeros((2, 2));
213 state_minus[[0, 0]] = Complex64::new(0.5, 0.0);
214 state_minus[[0, 1]] = Complex64::new(-0.5, 0.0);
215 state_minus[[1, 0]] = Complex64::new(-0.5, 0.0);
216 state_minus[[1, 1]] = Complex64::new(0.5, 0.0);
217 states.push(state_minus);
218
219 let mut state_plus_i = Array2::zeros((2, 2));
221 state_plus_i[[0, 0]] = Complex64::new(0.5, 0.0);
222 state_plus_i[[0, 1]] = Complex64::new(0.0, 0.5);
223 state_plus_i[[1, 0]] = Complex64::new(0.0, -0.5);
224 state_plus_i[[1, 1]] = Complex64::new(0.5, 0.0);
225 states.push(state_plus_i);
226
227 let mut state_minus_i = Array2::zeros((2, 2));
229 state_minus_i[[0, 0]] = Complex64::new(0.5, 0.0);
230 state_minus_i[[0, 1]] = Complex64::new(0.0, -0.5);
231 state_minus_i[[1, 0]] = Complex64::new(0.0, 0.5);
232 state_minus_i[[1, 1]] = Complex64::new(0.5, 0.0);
233 states.push(state_minus_i);
234 } else {
235 let single_qubit_states = self.create_informationally_complete_states(1)?;
237
238 for combination in self.generate_state_combinations(&single_qubit_states, num_qubits)? {
240 states.push(combination);
241 }
242 }
243
244 Ok(states)
245 }
246
247 pub(crate) fn create_pauli_measurements(
249 &self,
250 num_qubits: usize,
251 ) -> DeviceResult<Vec<Array2<Complex64>>> {
252 let mut measurements = Vec::new();
253 let dim = 1 << num_qubits;
254
255 let pauli_i = Array2::from_shape_vec(
257 (2, 2),
258 vec![
259 Complex64::new(1.0, 0.0),
260 Complex64::new(0.0, 0.0),
261 Complex64::new(0.0, 0.0),
262 Complex64::new(1.0, 0.0),
263 ],
264 )
265 .map_err(|e| DeviceError::APIError(format!("Array creation error: {e}")))?;
266
267 let pauli_x = Array2::from_shape_vec(
268 (2, 2),
269 vec![
270 Complex64::new(0.0, 0.0),
271 Complex64::new(1.0, 0.0),
272 Complex64::new(1.0, 0.0),
273 Complex64::new(0.0, 0.0),
274 ],
275 )
276 .map_err(|e| DeviceError::APIError(format!("Array creation error: {e}")))?;
277
278 let pauli_y = Array2::from_shape_vec(
279 (2, 2),
280 vec![
281 Complex64::new(0.0, 0.0),
282 Complex64::new(0.0, -1.0),
283 Complex64::new(0.0, 1.0),
284 Complex64::new(0.0, 0.0),
285 ],
286 )
287 .map_err(|e| DeviceError::APIError(format!("Array creation error: {e}")))?;
288
289 let pauli_z = Array2::from_shape_vec(
290 (2, 2),
291 vec![
292 Complex64::new(1.0, 0.0),
293 Complex64::new(0.0, 0.0),
294 Complex64::new(0.0, 0.0),
295 Complex64::new(-1.0, 0.0),
296 ],
297 )
298 .map_err(|e| DeviceError::APIError(format!("Array creation error: {e}")))?;
299
300 let single_qubit_paulis = vec![pauli_i, pauli_x, pauli_y, pauli_z];
301
302 if num_qubits == 1 {
303 measurements = single_qubit_paulis;
304 } else {
305 measurements = self.generate_pauli_tensor_products(&single_qubit_paulis, num_qubits)?;
307 }
308
309 Ok(measurements)
310 }
311
312 pub fn calculate_process_fidelity(
314 &self,
315 process_matrix: &Array4<Complex64>,
316 ) -> DeviceResult<f64> {
317 let choi_matrix = self.convert_to_choi_matrix(process_matrix)?;
319
320 let ideal_choi = self.create_ideal_identity_choi(choi_matrix.dim().0)?;
322
323 #[cfg(feature = "scirs2")]
325 {
326 let fidelity = self.calculate_choi_fidelity(&choi_matrix, &ideal_choi)?;
327 Ok(fidelity)
328 }
329
330 #[cfg(not(feature = "scirs2"))]
331 {
332 Ok(0.95) }
335 }
336
337 pub fn calculate_entangling_power(
339 &self,
340 process_matrix: &Array4<Complex64>,
341 ) -> DeviceResult<f64> {
342 let dim = process_matrix.dim().0;
343
344 if dim <= 2 {
346 return Ok(0.0);
347 }
348
349 #[cfg(feature = "scirs2")]
350 {
351 let choi_matrix = self.convert_to_choi_matrix(process_matrix)?;
353
354 let partial_transpose = self.partial_transpose(&choi_matrix)?;
356
357 if let Ok(eigenvalues) = eigvals(&partial_transpose.view(), None) {
358 let negative_eigenvalues: Vec<f64> = eigenvalues
359 .iter()
360 .map(|x| x.re)
361 .filter(|&x| x < 0.0)
362 .collect();
363
364 let entangling_power = negative_eigenvalues.iter().map(|x| x.abs()).sum::<f64>();
365 Ok(entangling_power)
366 } else {
367 Ok(0.0)
368 }
369 }
370
371 #[cfg(not(feature = "scirs2"))]
372 {
373 let mut entangling_power = 0.0;
375 for i in 0..dim {
376 for j in 0..dim {
377 for k in 0..dim {
378 for l in 0..dim {
379 if i != j || k != l {
380 entangling_power += process_matrix[[i, j, k, l]].norm_sqr();
381 }
382 }
383 }
384 }
385 }
386 Ok(entangling_power / (dim * dim) as f64)
387 }
388 }
389}
390
391pub trait ProcessTomographyExecutor {
393 fn execute_process_measurement<const N: usize>(
395 &self,
396 circuit: &Circuit<N>,
397 input_state: &Array2<Complex64>,
398 measurement: &Array2<Complex64>,
399 shots: usize,
400 ) -> impl std::future::Future<Output = DeviceResult<f64>> + Send;
401}