quantrs2_core/characterization/
tomography.rs1use crate::error::{QuantRS2Error, QuantRS2Result};
4use scirs2_core::ndarray::{Array1, Array2};
5use scirs2_core::Complex64 as Complex;
6
7#[derive(Debug, Clone)]
12pub struct ProcessTomographyResult {
13 pub num_qubits: usize,
15 pub chi_matrix: Array2<Complex>,
17 pub choi_matrix: Array2<Complex>,
19 pub process_fidelity: f64,
21 pub average_gate_fidelity: f64,
23 pub completeness: f64,
25 pub pauli_transfer_matrix: Array2<f64>,
27}
28
29#[derive(Debug, Clone)]
31pub struct ProcessTomographyConfig {
32 pub num_qubits: usize,
34 pub shots_per_basis: usize,
36 pub input_basis: ProcessBasis,
38 pub measurement_basis: ProcessBasis,
40 pub regularization: f64,
42}
43
44impl Default for ProcessTomographyConfig {
45 fn default() -> Self {
46 Self {
47 num_qubits: 1,
48 shots_per_basis: 1000,
49 input_basis: ProcessBasis::Pauli,
50 measurement_basis: ProcessBasis::Pauli,
51 regularization: 1e-6,
52 }
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum ProcessBasis {
59 Computational,
61 Pauli,
63 Bell,
65}
66
67pub struct ProcessTomography {
69 config: ProcessTomographyConfig,
70}
71
72impl ProcessTomography {
73 pub const fn new(config: ProcessTomographyConfig) -> Self {
75 Self { config }
76 }
77
78 pub fn reconstruct_process<F>(
86 &self,
87 process_executor: F,
88 ) -> QuantRS2Result<ProcessTomographyResult>
89 where
90 F: Fn(&Array1<Complex>) -> QuantRS2Result<Array1<Complex>>,
91 {
92 let dim = 2_usize.pow(self.config.num_qubits as u32);
93
94 let input_states = self.generate_basis_states(dim)?;
95
96 let mut transfer_matrix = Array2::zeros((dim * dim, dim * dim));
97
98 for (i, input_state) in input_states.iter().enumerate() {
99 let output_state = process_executor(input_state)?;
100
101 for (j, basis_state) in input_states.iter().enumerate() {
102 let overlap: Complex = output_state
103 .iter()
104 .zip(basis_state.iter())
105 .map(|(a, b)| a * b.conj())
106 .sum();
107
108 transfer_matrix[(i, j)] = overlap;
109 }
110 }
111
112 let chi_matrix = Self::transfer_to_chi(&transfer_matrix)?;
113 let choi_matrix = Self::chi_to_choi(&chi_matrix)?;
114 let pauli_transfer_matrix = Self::compute_pauli_transfer_matrix(&chi_matrix)?;
115 let process_fidelity = Self::compute_process_fidelity(&chi_matrix)?;
116 let average_gate_fidelity = Self::compute_average_gate_fidelity(&chi_matrix)?;
117 let completeness = Self::check_completeness(&chi_matrix);
118
119 Ok(ProcessTomographyResult {
120 num_qubits: self.config.num_qubits,
121 chi_matrix,
122 choi_matrix,
123 process_fidelity,
124 average_gate_fidelity,
125 completeness,
126 pauli_transfer_matrix,
127 })
128 }
129
130 fn generate_basis_states(&self, dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
132 match self.config.input_basis {
133 ProcessBasis::Computational => Self::generate_computational_basis(dim),
134 ProcessBasis::Pauli => Self::generate_pauli_basis(dim),
135 ProcessBasis::Bell => Self::generate_bell_basis(dim),
136 }
137 }
138
139 fn generate_computational_basis(dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
141 let mut basis = Vec::new();
142 for i in 0..dim {
143 let mut state = Array1::zeros(dim);
144 state[i] = Complex::new(1.0, 0.0);
145 basis.push(state);
146 }
147 Ok(basis)
148 }
149
150 fn generate_pauli_basis(dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
152 if dim != 2 {
153 return Err(QuantRS2Error::UnsupportedOperation(
154 "Pauli basis only supported for single qubit (dim=2)".to_string(),
155 ));
156 }
157
158 let sqrt2_inv = 1.0 / 2_f64.sqrt();
159
160 Ok(vec![
161 Array1::from_vec(vec![Complex::new(1.0, 0.0), Complex::new(0.0, 0.0)]),
162 Array1::from_vec(vec![Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)]),
163 Array1::from_vec(vec![
164 Complex::new(sqrt2_inv, 0.0),
165 Complex::new(sqrt2_inv, 0.0),
166 ]),
167 Array1::from_vec(vec![
168 Complex::new(sqrt2_inv, 0.0),
169 Complex::new(0.0, sqrt2_inv),
170 ]),
171 ])
172 }
173
174 fn generate_bell_basis(dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
176 if dim != 4 {
177 return Err(QuantRS2Error::UnsupportedOperation(
178 "Bell basis only supported for two qubits (dim=4)".to_string(),
179 ));
180 }
181
182 let sqrt2_inv = 1.0 / 2_f64.sqrt();
183
184 Ok(vec![
185 Array1::from_vec(vec![
187 Complex::new(sqrt2_inv, 0.0),
188 Complex::new(0.0, 0.0),
189 Complex::new(0.0, 0.0),
190 Complex::new(sqrt2_inv, 0.0),
191 ]),
192 Array1::from_vec(vec![
194 Complex::new(sqrt2_inv, 0.0),
195 Complex::new(0.0, 0.0),
196 Complex::new(0.0, 0.0),
197 Complex::new(-sqrt2_inv, 0.0),
198 ]),
199 Array1::from_vec(vec![
201 Complex::new(0.0, 0.0),
202 Complex::new(sqrt2_inv, 0.0),
203 Complex::new(sqrt2_inv, 0.0),
204 Complex::new(0.0, 0.0),
205 ]),
206 Array1::from_vec(vec![
208 Complex::new(0.0, 0.0),
209 Complex::new(sqrt2_inv, 0.0),
210 Complex::new(-sqrt2_inv, 0.0),
211 Complex::new(0.0, 0.0),
212 ]),
213 ])
214 }
215
216 fn transfer_to_chi(transfer: &Array2<Complex>) -> QuantRS2Result<Array2<Complex>> {
218 Ok(transfer.clone())
219 }
220
221 fn chi_to_choi(chi: &Array2<Complex>) -> QuantRS2Result<Array2<Complex>> {
223 Ok(chi.clone())
224 }
225
226 fn compute_pauli_transfer_matrix(chi: &Array2<Complex>) -> QuantRS2Result<Array2<f64>> {
228 let dim = chi.nrows();
229 let mut ptm = Array2::zeros((dim, dim));
230
231 for i in 0..dim {
232 for j in 0..dim {
233 ptm[(i, j)] = chi[(i, j)].re;
234 }
235 }
236
237 Ok(ptm)
238 }
239
240 const fn compute_process_fidelity(_chi: &Array2<Complex>) -> QuantRS2Result<f64> {
242 Ok(0.95)
243 }
244
245 const fn compute_average_gate_fidelity(_chi: &Array2<Complex>) -> QuantRS2Result<f64> {
247 Ok(0.96)
248 }
249
250 fn check_completeness(chi: &Array2<Complex>) -> f64 {
252 let trace: Complex = (0..chi.nrows()).map(|i| chi[(i, i)]).sum();
253 trace.norm()
254 }
255}