quantrs2_device/process_tomography/
utils.rs1use scirs2_core::ndarray::{Array1, Array2, Array4};
4use scirs2_core::Complex64;
5use std::collections::HashMap;
6
7use super::core::SciRS2ProcessTomographer;
8use super::results::*;
9use crate::DeviceResult;
10
11impl SciRS2ProcessTomographer {
12 pub async fn collect_experimental_data<
14 const N: usize,
15 E: super::core::ProcessTomographyExecutor,
16 >(
17 &self,
18 process_circuit: &quantrs2_circuit::prelude::Circuit<N>,
19 executor: &E,
20 ) -> DeviceResult<ExperimentalData> {
21 let mut measurement_results = Vec::new();
22 let mut measurement_uncertainties = Vec::new();
23
24 for input_state in &self.input_states {
26 for measurement_op in &self.measurement_operators {
27 let result = executor
28 .execute_process_measurement(
29 process_circuit,
30 input_state,
31 measurement_op,
32 self.config.shots_per_state,
33 )
34 .await?;
35
36 measurement_results.push(result);
37
38 let uncertainty = if result > 0.0 {
40 (result / self.config.shots_per_state as f64).sqrt()
41 } else {
42 1.0 / (self.config.shots_per_state as f64).sqrt()
43 };
44 measurement_uncertainties.push(uncertainty);
45 }
46 }
47
48 Ok(ExperimentalData {
49 input_states: self.input_states.clone(),
50 measurement_operators: self.measurement_operators.clone(),
51 measurement_results,
52 measurement_uncertainties,
53 })
54 }
55
56 pub(crate) fn create_ideal_identity_choi(&self, dim: usize) -> DeviceResult<Array2<Complex64>> {
58 let choi_dim = dim;
59 let mut identity_choi = Array2::zeros((choi_dim, choi_dim));
60
61 for i in 0..choi_dim {
63 identity_choi[[i, i]] = Complex64::new(1.0, 0.0);
64 }
65
66 Ok(identity_choi)
67 }
68
69 pub(crate) fn calculate_choi_fidelity(
71 &self,
72 choi1: &Array2<Complex64>,
73 choi2: &Array2<Complex64>,
74 ) -> DeviceResult<f64> {
75 let dim = choi1.nrows();
76 let mut fidelity = 0.0;
77 let mut norm1 = 0.0;
78 let mut norm2 = 0.0;
79
80 for i in 0..dim {
81 for j in 0..dim {
82 let element1 = choi1[[i, j]];
83 let element2 = choi2[[i, j]];
84
85 fidelity += (element1.conj() * element2).re;
86 norm1 += element1.norm_sqr();
87 norm2 += element2.norm_sqr();
88 }
89 }
90
91 if norm1 > 1e-12 && norm2 > 1e-12 {
92 Ok(fidelity / (norm1 * norm2).sqrt())
93 } else {
94 Ok(0.0)
95 }
96 }
97
98 pub(crate) fn partial_transpose(
100 &self,
101 matrix: &Array2<Complex64>,
102 ) -> DeviceResult<Array2<f64>> {
103 let dim = matrix.nrows();
104 let sqrt_dim = (dim as f64).sqrt() as usize;
105
106 if sqrt_dim * sqrt_dim != dim {
107 return Ok(Array2::zeros((dim, dim)));
108 }
109
110 let mut pt_matrix = Array2::zeros((dim, dim));
111
112 for i in 0..sqrt_dim {
114 for j in 0..sqrt_dim {
115 for k in 0..sqrt_dim {
116 for l in 0..sqrt_dim {
117 let row1 = i * sqrt_dim + j;
118 let col1 = k * sqrt_dim + l;
119 let row2 = i * sqrt_dim + l;
120 let col2 = k * sqrt_dim + j;
121
122 if row1 < dim && col1 < dim && row2 < dim && col2 < dim {
123 pt_matrix[[row2, col2]] = matrix[[row1, col1]].re;
124 }
125 }
126 }
127 }
128 }
129
130 Ok(pt_matrix)
131 }
132
133 pub fn calculate_process_metrics(
135 &self,
136 process_matrix: &Array4<Complex64>,
137 ) -> DeviceResult<ProcessMetrics> {
138 let process_fidelity = self.calculate_process_fidelity(process_matrix)?;
139 let average_gate_fidelity = self.calculate_average_gate_fidelity(process_matrix)?;
140 let unitarity = self.calculate_unitarity(process_matrix)?;
141 let entangling_power = self.calculate_entangling_power(process_matrix)?;
142 let non_unitality = self.calculate_non_unitality(process_matrix)?;
143 let channel_capacity = self.calculate_channel_capacity(process_matrix)?;
144 let coherent_information = self.calculate_coherent_information(process_matrix)?;
145 let diamond_norm_distance = self.calculate_diamond_norm_distance(process_matrix)?;
146 let process_spectrum = self.calculate_process_spectrum(process_matrix)?;
147
148 Ok(ProcessMetrics {
149 process_fidelity,
150 average_gate_fidelity,
151 unitarity,
152 entangling_power,
153 non_unitality,
154 channel_capacity,
155 coherent_information,
156 diamond_norm_distance,
157 process_spectrum,
158 })
159 }
160
161 fn calculate_average_gate_fidelity(
163 &self,
164 process_matrix: &Array4<Complex64>,
165 ) -> DeviceResult<f64> {
166 let dim = process_matrix.dim().0;
167
168 let process_fidelity = self.calculate_process_fidelity(process_matrix)?;
170 let agf = (dim as f64).mul_add(process_fidelity, 1.0) / (dim as f64 + 1.0);
171
172 Ok(agf)
173 }
174
175 fn calculate_unitarity(&self, process_matrix: &Array4<Complex64>) -> DeviceResult<f64> {
177 let dim = process_matrix.dim().0;
178 let mut unitarity = 0.0;
179
180 for i in 0..dim {
182 for j in 0..dim {
183 for k in 0..dim {
184 for l in 0..dim {
185 for m in 0..dim {
186 for n in 0..dim {
187 unitarity += (process_matrix[[i, j, k, l]].conj()
188 * process_matrix[[i, j, m, n]]
189 * process_matrix[[m, n, k, l]])
190 .re;
191 }
192 }
193 }
194 }
195 }
196 }
197
198 Ok(unitarity / (dim * dim) as f64)
199 }
200
201 fn calculate_non_unitality(&self, process_matrix: &Array4<Complex64>) -> DeviceResult<f64> {
203 let dim = process_matrix.dim().0;
204 let mut non_unitality = 0.0;
205
206 for i in 0..dim {
208 for j in 0..dim {
209 if i != j {
210 let mut sum = Complex64::new(0.0, 0.0);
211 for k in 0..dim {
212 sum += process_matrix[[i, j, k, k]];
213 }
214 non_unitality += sum.norm_sqr();
215 }
216 }
217 }
218
219 Ok(non_unitality / (dim * dim) as f64)
220 }
221
222 fn calculate_channel_capacity(&self, process_matrix: &Array4<Complex64>) -> DeviceResult<f64> {
224 let unitarity = self.calculate_unitarity(process_matrix)?;
227 let capacity = unitarity * (process_matrix.dim().0 as f64).log2();
228
229 Ok(capacity)
230 }
231
232 fn calculate_coherent_information(
234 &self,
235 process_matrix: &Array4<Complex64>,
236 ) -> DeviceResult<f64> {
237 let unitarity = self.calculate_unitarity(process_matrix)?;
239 let coherent_info = unitarity * 0.8; Ok(coherent_info)
242 }
243
244 fn calculate_diamond_norm_distance(
246 &self,
247 process_matrix: &Array4<Complex64>,
248 ) -> DeviceResult<f64> {
249 let process_fidelity = self.calculate_process_fidelity(process_matrix)?;
252 let diamond_distance = 2.0 * (1.0 - process_fidelity).sqrt();
253
254 Ok(diamond_distance)
255 }
256
257 fn calculate_process_spectrum(
259 &self,
260 process_matrix: &Array4<Complex64>,
261 ) -> DeviceResult<Array1<f64>> {
262 let choi_matrix = self.convert_to_choi_matrix(process_matrix)?;
263
264 #[cfg(feature = "scirs2")]
265 {
266 use scirs2_linalg::eigvals;
267
268 let real_choi = choi_matrix.mapv(|x| x.re);
269 if let Ok(eigenvalues) = eigvals(&real_choi.view(), None) {
270 let spectrum = eigenvalues.mapv(|x| x.re);
271 return Ok(spectrum);
272 }
273 }
274
275 let dim = choi_matrix.nrows();
277 Ok(Array1::from_elem(dim, 1.0 / dim as f64))
278 }
279}
280
281pub mod process_utils {
283 use super::*;
284
285 pub fn reshape_process_matrix(
287 process_matrix: &Array4<Complex64>,
288 target_shape: (usize, usize),
289 ) -> DeviceResult<Array2<Complex64>> {
290 let (dim1, dim2, dim3, dim4) = process_matrix.dim();
291 let total_elements = dim1 * dim2 * dim3 * dim4;
292
293 if target_shape.0 * target_shape.1 != total_elements {
294 return Err(crate::DeviceError::APIError(
295 "Target shape incompatible with process matrix size".to_string(),
296 ));
297 }
298
299 let mut reshaped = Array2::zeros(target_shape);
300 let mut idx = 0;
301
302 for i in 0..dim1 {
303 for j in 0..dim2 {
304 for k in 0..dim3 {
305 for l in 0..dim4 {
306 let row = idx / target_shape.1;
307 let col = idx % target_shape.1;
308
309 if row < target_shape.0 && col < target_shape.1 {
310 reshaped[[row, col]] = process_matrix[[i, j, k, l]];
311 }
312 idx += 1;
313 }
314 }
315 }
316 }
317
318 Ok(reshaped)
319 }
320
321 pub fn vectorize_process_matrix(process_matrix: &Array4<Complex64>) -> Array1<Complex64> {
323 let (dim1, dim2, dim3, dim4) = process_matrix.dim();
324 let total_elements = dim1 * dim2 * dim3 * dim4;
325 let mut vector = Array1::zeros(total_elements);
326
327 let mut idx = 0;
328 for i in 0..dim1 {
329 for j in 0..dim2 {
330 for k in 0..dim3 {
331 for l in 0..dim4 {
332 vector[idx] = process_matrix[[i, j, k, l]];
333 idx += 1;
334 }
335 }
336 }
337 }
338
339 vector
340 }
341
342 pub fn devectorize_to_process_matrix(
344 vector: &Array1<Complex64>,
345 dim: usize,
346 ) -> DeviceResult<Array4<Complex64>> {
347 let expected_length = dim.pow(4);
348 if vector.len() != expected_length {
349 return Err(crate::DeviceError::APIError(format!(
350 "Vector length {} does not match expected length {} for dimension {}",
351 vector.len(),
352 expected_length,
353 dim
354 )));
355 }
356
357 let mut process_matrix = Array4::zeros((dim, dim, dim, dim));
358 let mut idx = 0;
359
360 for i in 0..dim {
361 for j in 0..dim {
362 for k in 0..dim {
363 for l in 0..dim {
364 process_matrix[[i, j, k, l]] = vector[idx];
365 idx += 1;
366 }
367 }
368 }
369 }
370
371 Ok(process_matrix)
372 }
373
374 pub fn trace_distance(
376 process1: &Array4<Complex64>,
377 process2: &Array4<Complex64>,
378 ) -> DeviceResult<f64> {
379 if process1.dim() != process2.dim() {
380 return Err(crate::DeviceError::APIError(
381 "Process matrices must have the same dimensions".to_string(),
382 ));
383 }
384
385 let dim = process1.dim();
386 let mut trace_distance = 0.0;
387
388 for i in 0..dim.0 {
389 for j in 0..dim.1 {
390 for k in 0..dim.2 {
391 for l in 0..dim.3 {
392 let diff = process1[[i, j, k, l]] - process2[[i, j, k, l]];
393 trace_distance += diff.norm();
394 }
395 }
396 }
397 }
398
399 Ok(trace_distance / 2.0)
400 }
401
402 pub fn check_trace_preservation(process_matrix: &Array4<Complex64>, tolerance: f64) -> bool {
404 let dim = process_matrix.dim().0;
405 let mut trace = Complex64::new(0.0, 0.0);
406
407 for i in 0..dim {
408 for j in 0..dim {
409 trace += process_matrix[[i, j, i, j]];
410 }
411 }
412
413 (trace.re - 1.0).abs() < tolerance && trace.im.abs() < tolerance
414 }
415
416 pub fn check_complete_positivity(process_matrix: &Array4<Complex64>, tolerance: f64) -> bool {
418 let dim = process_matrix.dim().0;
419
420 for i in 0..dim {
422 for j in 0..dim {
423 if process_matrix[[i, j, i, j]].re < -tolerance {
424 return false;
425 }
426 }
427 }
428
429 true
430 }
431
432 pub fn normalize_process_matrix(process_matrix: &mut Array4<Complex64>) {
434 let dim = process_matrix.dim().0;
435 let mut trace = Complex64::new(0.0, 0.0);
436
437 for i in 0..dim {
439 for j in 0..dim {
440 trace += process_matrix[[i, j, i, j]];
441 }
442 }
443
444 if trace.norm() > 1e-12 {
446 let scale_factor = Complex64::new(1.0, 0.0) / trace;
447 for i in 0..dim {
448 for j in 0..dim {
449 for k in 0..dim {
450 for l in 0..dim {
451 process_matrix[[i, j, k, l]] *= scale_factor;
452 }
453 }
454 }
455 }
456 }
457 }
458}