quantrs2_device/process_tomography/analysis/
statistical.rs1use scirs2_core::ndarray::{Array1, Array2, Array4, ArrayView1};
4use scirs2_core::Complex64;
5use std::collections::HashMap;
6
7use super::super::core::SciRS2ProcessTomographer;
8use super::super::results::*;
9use crate::DeviceResult;
10
11#[cfg(feature = "scirs2")]
13use scirs2_stats::{mean, std, var};
14
15#[cfg(feature = "scirs2")]
16use scirs2_linalg::eigvals;
17
18#[cfg(not(feature = "scirs2"))]
19use super::super::fallback::{mean, std, var};
20
21impl SciRS2ProcessTomographer {
22 pub(crate) fn fit_distribution(
24 &self,
25 data: &[f64],
26 name: &str,
27 ) -> DeviceResult<ElementDistribution> {
28 #[cfg(feature = "scirs2")]
29 {
30 let data_array = Array1::from_vec(data.to_vec());
31 let data_view = data_array.view();
32
33 let mean_val = mean(&data_view).unwrap_or(0.0);
34 let std_val = std(&data_view, 0, None).unwrap_or(1.0);
35
36 let mut goodness_of_fit = 0.0;
38 let mut distribution_type = "normal".to_string();
39 let mut parameters = vec![mean_val, std_val];
40
41 if data.iter().all(|&x| x >= 0.0) {
43 let gamma_alpha = mean_val * mean_val / (std_val * std_val);
45 let gamma_beta = mean_val / (std_val * std_val);
46
47 if gamma_alpha > 0.0 && gamma_beta > 0.0 {
48 distribution_type = "gamma".to_string();
49 parameters = vec![gamma_alpha, gamma_beta];
50 goodness_of_fit = 0.85; }
52 }
53
54 let confidence_interval = (
56 mean_val - 1.96 * std_val / (data.len() as f64).sqrt(),
57 mean_val + 1.96 * std_val / (data.len() as f64).sqrt(),
58 );
59
60 Ok(ElementDistribution {
61 distribution_type,
62 parameters,
63 goodness_of_fit,
64 confidence_interval,
65 })
66 }
67
68 #[cfg(not(feature = "scirs2"))]
69 {
70 let mean_val = data.iter().sum::<f64>() / data.len() as f64;
72 let var_val =
73 data.iter().map(|x| (x - mean_val).powi(2)).sum::<f64>() / data.len() as f64;
74 let std_val = var_val.sqrt();
75
76 Ok(ElementDistribution {
77 distribution_type: "normal".to_string(),
78 parameters: vec![mean_val, std_val],
79 goodness_of_fit: 0.9,
80 confidence_interval: (mean_val - std_val, mean_val + std_val),
81 })
82 }
83 }
84
85 pub(crate) fn calculate_skewness(&self, data: &[f64]) -> f64 {
87 if data.len() < 3 {
88 return 0.0;
89 }
90
91 let n = data.len() as f64;
92 let mean = data.iter().sum::<f64>() / n;
93 let variance = data.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n;
94 let std_dev = variance.sqrt();
95
96 if std_dev < 1e-12 {
97 return 0.0;
98 }
99
100 let skewness = data
101 .iter()
102 .map(|&x| ((x - mean) / std_dev).powi(3))
103 .sum::<f64>()
104 / n;
105
106 skewness
107 }
108
109 pub(crate) fn calculate_kurtosis(&self, data: &[f64]) -> f64 {
111 if data.len() < 4 {
112 return 0.0;
113 }
114
115 let n = data.len() as f64;
116 let mean = data.iter().sum::<f64>() / n;
117 let variance = data.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n;
118 let std_dev = variance.sqrt();
119
120 if std_dev < 1e-12 {
121 return 0.0;
122 }
123
124 let kurtosis = data
125 .iter()
126 .map(|&x| ((x - mean) / std_dev).powi(4))
127 .sum::<f64>()
128 / n
129 - 3.0; kurtosis
132 }
133
134 pub(crate) fn calculate_entropy(&self, data: &[f64]) -> f64 {
136 if data.is_empty() {
137 return 0.0;
138 }
139
140 let num_bins = (data.len() as f64).sqrt() as usize + 1;
142 let min_val = data.iter().copied().fold(f64::INFINITY, f64::min);
143 let max_val = data.iter().copied().fold(f64::NEG_INFINITY, f64::max);
144
145 if (max_val - min_val).abs() < 1e-12 {
146 return 0.0;
147 }
148
149 let bin_width = (max_val - min_val) / num_bins as f64;
150 let mut histogram = vec![0; num_bins];
151
152 for &value in data {
153 let bin_idx = ((value - min_val) / bin_width).floor() as usize;
154 let bin_idx = bin_idx.min(num_bins - 1);
155 histogram[bin_idx] += 1;
156 }
157
158 let total_count = data.len() as f64;
160 let mut entropy = 0.0;
161
162 for &count in &histogram {
163 if count > 0 {
164 let probability = count as f64 / total_count;
165 entropy -= probability * probability.ln();
166 }
167 }
168
169 entropy
170 }
171
172 pub(crate) fn analyze_eigenvalue_distribution(
174 &self,
175 process_matrix: &Array4<Complex64>,
176 ) -> DeviceResult<ElementDistribution> {
177 let choi_matrix = self.convert_to_choi_matrix(process_matrix)?;
179
180 #[cfg(feature = "scirs2")]
181 {
182 use scirs2_linalg::eig;
183
184 let real_matrix = choi_matrix.mapv(|x| x.re);
186
187 if let Ok(eigenvalues) = eigvals(&real_matrix.view(), None) {
189 let real_eigenvalues: Vec<f64> = eigenvalues.iter().map(|x| x.re).collect();
190
191 return self.fit_distribution(&real_eigenvalues, "eigenvalues");
192 }
193 }
194
195 Ok(ElementDistribution {
197 distribution_type: "uniform".to_string(),
198 parameters: vec![0.0, 1.0],
199 goodness_of_fit: 0.8,
200 confidence_interval: (0.0, 1.0),
201 })
202 }
203
204 pub(crate) fn convert_to_choi_matrix(
206 &self,
207 process_matrix: &Array4<Complex64>,
208 ) -> DeviceResult<Array2<Complex64>> {
209 let dim = process_matrix.dim().0;
210 let choi_dim = dim * dim;
211 let mut choi_matrix = Array2::zeros((choi_dim, choi_dim));
212
213 for i in 0..dim {
216 for j in 0..dim {
217 for k in 0..dim {
218 for l in 0..dim {
219 let choi_row = i * dim + j;
220 let choi_col = k * dim + l;
221 if choi_row < choi_dim && choi_col < choi_dim {
222 choi_matrix[[choi_row, choi_col]] = process_matrix[[i, k, j, l]];
223 }
224 }
225 }
226 }
227 }
228
229 Ok(choi_matrix)
230 }
231
232 pub(crate) fn compute_process_coefficient(
234 &self,
235 input_state: &Array2<Complex64>,
236 measurement: &Array2<Complex64>,
237 i: usize,
238 j: usize,
239 k: usize,
240 l: usize,
241 ) -> DeviceResult<f64> {
242 let dim = input_state.dim().0;
246 if i >= dim || j >= dim || k >= dim || l >= dim {
247 return Ok(0.0);
248 }
249
250 let mut e_ij = Array2::zeros((dim, dim));
252 e_ij[[i, j]] = Complex64::new(1.0, 0.0);
253
254 let mut e_kl = Array2::zeros((dim, dim));
255 e_kl[[k, l]] = Complex64::new(1.0, 0.0);
256
257 let mut result = Complex64::new(0.0, 0.0);
259 for p in 0..dim {
260 for q in 0..dim {
261 for r in 0..dim {
262 for s in 0..dim {
263 result += measurement[[p, q]]
264 * e_ij[[q, r]]
265 * input_state[[r, s]]
266 * e_kl[[s, p]].conj();
267 }
268 }
269 }
270 }
271
272 Ok(result.re)
273 }
274
275 pub(crate) fn generate_state_combinations(
277 &self,
278 single_qubit_states: &[Array2<Complex64>],
279 num_qubits: usize,
280 ) -> DeviceResult<Vec<Array2<Complex64>>> {
281 if num_qubits == 1 {
282 return Ok(single_qubit_states.to_vec());
283 }
284
285 let mut combinations = Vec::new();
286 let num_states = single_qubit_states.len();
287 let total_combinations = num_states.pow(num_qubits as u32);
288
289 for combo_idx in 0..total_combinations {
290 let mut combination = single_qubit_states[0].clone();
291 let mut temp_idx = combo_idx;
292
293 for qubit_idx in 1..num_qubits {
294 temp_idx /= num_states;
295 let state_idx = temp_idx % num_states;
296 combination = self.tensor_product(&combination, &single_qubit_states[state_idx])?;
297 }
298
299 combinations.push(combination);
300 }
301
302 Ok(combinations)
303 }
304
305 pub(crate) fn tensor_product(
307 &self,
308 a: &Array2<Complex64>,
309 b: &Array2<Complex64>,
310 ) -> DeviceResult<Array2<Complex64>> {
311 let a_shape = a.dim();
312 let b_shape = b.dim();
313 let result_shape = (a_shape.0 * b_shape.0, a_shape.1 * b_shape.1);
314
315 let mut result = Array2::zeros(result_shape);
316
317 for i in 0..a_shape.0 {
318 for j in 0..a_shape.1 {
319 for k in 0..b_shape.0 {
320 for l in 0..b_shape.1 {
321 let result_i = i * b_shape.0 + k;
322 let result_j = j * b_shape.1 + l;
323 result[[result_i, result_j]] = a[[i, j]] * b[[k, l]];
324 }
325 }
326 }
327 }
328
329 Ok(result)
330 }
331
332 pub(crate) fn generate_pauli_tensor_products(
334 &self,
335 single_qubit_paulis: &[Array2<Complex64>],
336 num_qubits: usize,
337 ) -> DeviceResult<Vec<Array2<Complex64>>> {
338 if num_qubits == 1 {
339 return Ok(single_qubit_paulis.to_vec());
340 }
341
342 let mut tensor_products = Vec::new();
343 let num_paulis = single_qubit_paulis.len();
344 let total_products = num_paulis.pow(num_qubits as u32);
345
346 for product_idx in 0..total_products {
347 let mut product = single_qubit_paulis[0].clone();
348 let mut temp_idx = product_idx;
349
350 for qubit_idx in 1..num_qubits {
351 temp_idx /= num_paulis;
352 let pauli_idx = temp_idx % num_paulis;
353 product = self.tensor_product(&product, &single_qubit_paulis[pauli_idx])?;
354 }
355
356 tensor_products.push(product);
357 }
358
359 Ok(tensor_products)
360 }
361}