quantrs2_device/process_tomography/analysis/
mod.rs

1//! Analysis components for process tomography
2
3pub mod monitoring;
4pub mod statistical;
5pub mod structural;
6
7use scirs2_core::ndarray::{Array1, Array2, Array4};
8use scirs2_core::Complex64;
9use std::collections::HashMap;
10
11use super::core::SciRS2ProcessTomographer;
12use super::results::*;
13use crate::DeviceResult;
14
15pub use monitoring::*;
16pub use statistical::*;
17pub use structural::*;
18
19// Conditional imports
20#[cfg(feature = "scirs2")]
21use scirs2_stats::{
22    corrcoef, mean, pearsonr, shapiro_wilk, spearmanr, std, ttest::Alternative, ttest_1samp,
23    ttest_ind, var, TTestResult,
24};
25
26#[cfg(not(feature = "scirs2"))]
27use super::super::fallback::*;
28
29/// Analysis methods implementation for SciRS2ProcessTomographer
30impl SciRS2ProcessTomographer {
31    /// Perform comprehensive statistical analysis
32    pub fn perform_statistical_analysis(
33        &self,
34        process_matrix: &Array4<Complex64>,
35        experimental_data: &ExperimentalData,
36    ) -> DeviceResult<(
37        HashMap<String, StatisticalTest>,
38        DistributionAnalysis,
39        CorrelationAnalysis,
40    )> {
41        let statistical_tests =
42            self.perform_statistical_tests(process_matrix, experimental_data)?;
43        let distribution_analysis = self.analyze_distributions(process_matrix)?;
44        let correlation_analysis = self.analyze_correlations(process_matrix)?;
45
46        Ok((
47            statistical_tests,
48            distribution_analysis,
49            correlation_analysis,
50        ))
51    }
52
53    /// Perform statistical tests on the process
54    fn perform_statistical_tests(
55        &self,
56        process_matrix: &Array4<Complex64>,
57        experimental_data: &ExperimentalData,
58    ) -> DeviceResult<HashMap<String, StatisticalTest>> {
59        let mut tests = HashMap::new();
60
61        // Extract real and imaginary parts for testing
62        let dim = process_matrix.dim().0;
63        let mut real_parts = Vec::new();
64        let mut imag_parts = Vec::new();
65
66        for i in 0..dim {
67            for j in 0..dim {
68                for k in 0..dim {
69                    for l in 0..dim {
70                        real_parts.push(process_matrix[[i, j, k, l]].re);
71                        imag_parts.push(process_matrix[[i, j, k, l]].im);
72                    }
73                }
74            }
75        }
76
77        // Normality tests
78        #[cfg(feature = "scirs2")]
79        {
80            let real_array = scirs2_core::ndarray::Array1::from_vec(real_parts.clone());
81            if let Ok((statistic, pvalue)) = shapiro_wilk(&real_array.view()) {
82                tests.insert(
83                    "shapiro_wilk_real".to_string(),
84                    StatisticalTest {
85                        statistic,
86                        p_value: pvalue,
87                        critical_value: 0.95,
88                        is_significant: pvalue < 0.05,
89                        effect_size: Some(1.0 - statistic),
90                    },
91                );
92            }
93
94            let imag_array = scirs2_core::ndarray::Array1::from_vec(imag_parts.clone());
95            if let Ok((statistic, pvalue)) = shapiro_wilk(&imag_array.view()) {
96                tests.insert(
97                    "shapiro_wilk_imag".to_string(),
98                    StatisticalTest {
99                        statistic,
100                        p_value: pvalue,
101                        critical_value: 0.95,
102                        is_significant: pvalue < 0.05,
103                        effect_size: Some(1.0 - statistic),
104                    },
105                );
106            }
107        }
108
109        #[cfg(not(feature = "scirs2"))]
110        {
111            tests.insert(
112                "shapiro_wilk_real".to_string(),
113                StatisticalTest {
114                    statistic: 0.95,
115                    p_value: 0.1,
116                    critical_value: 0.95,
117                    is_significant: false,
118                    effect_size: Some(0.05),
119                },
120            );
121        }
122
123        // T-tests comparing real and imaginary parts
124        #[cfg(feature = "scirs2")]
125        {
126            let real_array = scirs2_core::ndarray::Array1::from_vec(real_parts);
127            let imag_array = scirs2_core::ndarray::Array1::from_vec(imag_parts);
128
129            if let Ok(ttest_result) = ttest_ind(
130                &real_array.view(),
131                &imag_array.view(),
132                true,
133                Alternative::TwoSided,
134                "",
135            ) {
136                tests.insert(
137                    "ttest_real_vs_imag".to_string(),
138                    StatisticalTest {
139                        statistic: ttest_result.statistic,
140                        p_value: ttest_result.pvalue,
141                        critical_value: 1.96,
142                        is_significant: ttest_result.pvalue < 0.05,
143                        effect_size: Some(ttest_result.statistic.abs() / 10.0),
144                    },
145                );
146            }
147        }
148
149        Ok(tests)
150    }
151
152    /// Analyze distributions of process matrix elements
153    fn analyze_distributions(
154        &self,
155        process_matrix: &Array4<Complex64>,
156    ) -> DeviceResult<DistributionAnalysis> {
157        let dim = process_matrix.dim().0;
158        let mut element_distributions = HashMap::new();
159
160        // Analyze real parts
161        let real_parts: Vec<f64> = (0..dim)
162            .flat_map(|i| {
163                (0..dim).flat_map(move |j| {
164                    (0..dim)
165                        .flat_map(move |k| (0..dim).map(move |l| process_matrix[[i, j, k, l]].re))
166                })
167            })
168            .collect();
169
170        let real_distribution = self.fit_distribution(&real_parts, "real_parts")?;
171        element_distributions.insert("real_parts".to_string(), real_distribution);
172
173        // Analyze imaginary parts
174        let imag_parts: Vec<f64> = (0..dim)
175            .flat_map(|i| {
176                (0..dim).flat_map(move |j| {
177                    (0..dim)
178                        .flat_map(move |k| (0..dim).map(move |l| process_matrix[[i, j, k, l]].im))
179                })
180            })
181            .collect();
182
183        let imag_distribution = self.fit_distribution(&imag_parts, "imag_parts")?;
184        element_distributions.insert("imag_parts".to_string(), imag_distribution);
185
186        // Analyze magnitudes
187        let magnitudes: Vec<f64> = (0..dim)
188            .flat_map(|i| {
189                (0..dim).flat_map(move |j| {
190                    (0..dim).flat_map(move |k| {
191                        (0..dim).map(move |l| process_matrix[[i, j, k, l]].norm())
192                    })
193                })
194            })
195            .collect();
196
197        let magnitude_distribution = self.fit_distribution(&magnitudes, "magnitudes")?;
198        element_distributions.insert("magnitudes".to_string(), magnitude_distribution);
199
200        // Calculate global properties
201        let skewness = self.calculate_skewness(&real_parts);
202        let kurtosis = self.calculate_kurtosis(&real_parts);
203        let entropy = self.calculate_entropy(&magnitudes);
204
205        let global_properties = GlobalDistributionProperties {
206            skewness,
207            kurtosis,
208            entropy,
209        };
210
211        Ok(DistributionAnalysis {
212            element_distributions,
213            global_properties,
214        })
215    }
216
217    /// Analyze correlations between process elements
218    fn analyze_correlations(
219        &self,
220        process_matrix: &Array4<Complex64>,
221    ) -> DeviceResult<CorrelationAnalysis> {
222        let dim = process_matrix.dim().0;
223        let mut element_correlations = HashMap::new();
224
225        // Extract vectors for correlation analysis
226        let real_parts: Vec<f64> = (0..dim)
227            .flat_map(|i| {
228                (0..dim).flat_map(move |j| {
229                    (0..dim)
230                        .flat_map(move |k| (0..dim).map(move |l| process_matrix[[i, j, k, l]].re))
231                })
232            })
233            .collect();
234
235        let imag_parts: Vec<f64> = (0..dim)
236            .flat_map(|i| {
237                (0..dim).flat_map(move |j| {
238                    (0..dim)
239                        .flat_map(move |k| (0..dim).map(move |l| process_matrix[[i, j, k, l]].im))
240                })
241            })
242            .collect();
243
244        let magnitudes: Vec<f64> = (0..dim)
245            .flat_map(|i| {
246                (0..dim).flat_map(move |j| {
247                    (0..dim).flat_map(move |k| {
248                        (0..dim).map(move |l| process_matrix[[i, j, k, l]].norm())
249                    })
250                })
251            })
252            .collect();
253
254        // Calculate correlations
255        #[cfg(feature = "scirs2")]
256        {
257            let real_array = scirs2_core::ndarray::Array1::from_vec(real_parts);
258            let imag_array = scirs2_core::ndarray::Array1::from_vec(imag_parts);
259            let mag_array = scirs2_core::ndarray::Array1::from_vec(magnitudes);
260
261            if let Ok((corr, p_val)) = pearsonr(&real_array.view(), &imag_array.view(), "two-sided")
262            {
263                element_correlations.insert("real_imag_correlation".to_string(), corr);
264            }
265
266            if let Ok((corr, p_val)) = pearsonr(&real_array.view(), &mag_array.view(), "two-sided")
267            {
268                element_correlations.insert("real_magnitude_correlation".to_string(), corr);
269            }
270
271            if let Ok((corr, p_val)) = pearsonr(&imag_array.view(), &mag_array.view(), "two-sided")
272            {
273                element_correlations.insert("imag_magnitude_correlation".to_string(), corr);
274            }
275        }
276
277        #[cfg(not(feature = "scirs2"))]
278        {
279            element_correlations.insert("real_imag_correlation".to_string(), 0.1);
280            element_correlations.insert("real_magnitude_correlation".to_string(), 0.8);
281            element_correlations.insert("imag_magnitude_correlation".to_string(), 0.2);
282        }
283
284        // Principal component analysis (simplified)
285        let n_components = 3;
286        let principal_components = Array2::eye(n_components);
287
288        // Correlation network (simplified)
289        let correlation_network = CorrelationNetwork {
290            adjacency_matrix: Array2::eye(3),
291            centrality_measures: HashMap::new(),
292        };
293
294        Ok(CorrelationAnalysis {
295            element_correlations,
296            principal_components,
297            correlation_network,
298        })
299    }
300
301    /// Analyze process structure
302    pub fn analyze_process_structure(
303        &self,
304        process_matrix: &Array4<Complex64>,
305    ) -> DeviceResult<ProcessStructureAnalysis> {
306        let kraus_decomposition = self.perform_kraus_decomposition(process_matrix)?;
307        let noise_decomposition = self.analyze_noise_decomposition(process_matrix)?;
308        let coherence_analysis = self.analyze_coherence(process_matrix)?;
309        let symmetry_analysis = self.analyze_symmetries(process_matrix)?;
310        let process_graph = self.construct_process_graph(process_matrix)?;
311
312        Ok(ProcessStructureAnalysis {
313            kraus_decomposition,
314            noise_decomposition,
315            coherence_analysis,
316            symmetry_analysis,
317            process_graph,
318        })
319    }
320
321    /// Convert to Pauli transfer representation
322    pub fn convert_to_pauli_transfer(
323        &self,
324        process_matrix: &Array4<Complex64>,
325    ) -> DeviceResult<Array2<f64>> {
326        let dim = process_matrix.dim().0;
327        let pauli_dim = dim * dim;
328        let mut pauli_transfer = Array2::zeros((pauli_dim, pauli_dim));
329
330        // Simplified conversion to Pauli transfer matrix
331        // In practice, this would involve proper Pauli basis transformations
332        for i in 0..pauli_dim.min(dim) {
333            for j in 0..pauli_dim.min(dim) {
334                if i < dim && j < dim {
335                    pauli_transfer[[i, j]] = process_matrix[[i, j, i, j]].re;
336                }
337            }
338        }
339
340        Ok(pauli_transfer)
341    }
342
343    /// Quantify uncertainty in process estimates
344    pub fn quantify_uncertainty(
345        &self,
346        process_matrix: &Array4<Complex64>,
347        experimental_data: &ExperimentalData,
348    ) -> DeviceResult<ProcessUncertaintyQuantification> {
349        let dim = process_matrix.dim().0;
350
351        // Bootstrap uncertainty estimation (simplified)
352        let mut confidence_intervals = Array4::from_elem((dim, dim, dim, dim), (0.0, 0.0));
353        let mut bootstrap_uncertainty = Array4::zeros((dim, dim, dim, dim));
354
355        for i in 0..dim {
356            for j in 0..dim {
357                for k in 0..dim {
358                    for l in 0..dim {
359                        let element_value = process_matrix[[i, j, k, l]].norm();
360                        let uncertainty = element_value * 0.1; // 10% uncertainty
361                        confidence_intervals[[i, j, k, l]] = (
362                            1.96f64.mul_add(-uncertainty, element_value),
363                            1.96f64.mul_add(uncertainty, element_value),
364                        );
365                        bootstrap_uncertainty[[i, j, k, l]] = uncertainty;
366                    }
367                }
368            }
369        }
370
371        // Fisher information matrix (simplified)
372        let fisher_info_dim = dim * dim;
373        let fisher_information = Array2::eye(fisher_info_dim);
374
375        Ok(ProcessUncertaintyQuantification {
376            confidence_intervals,
377            bootstrap_uncertainty,
378            fisher_information,
379        })
380    }
381
382    /// Compare with known processes
383    pub fn compare_with_known_processes(
384        &self,
385        process_matrix: &Array4<Complex64>,
386    ) -> DeviceResult<ProcessComparisons> {
387        let mut standard_process_fidelities = HashMap::new();
388        let mut process_distances = HashMap::new();
389        let mut model_selection_scores = HashMap::new();
390
391        // Compare with identity process
392        let identity_fidelity = self.calculate_process_fidelity(process_matrix)?;
393        standard_process_fidelities.insert("identity".to_string(), identity_fidelity);
394        process_distances.insert("identity".to_string(), 1.0 - identity_fidelity);
395
396        // Compare with Pauli channels (simplified)
397        let pauli_channels = ["pauli_x", "pauli_y", "pauli_z"];
398        for channel in &pauli_channels {
399            let fidelity = 0.5; // Placeholder
400            standard_process_fidelities.insert(channel.to_string(), fidelity);
401            process_distances.insert(channel.to_string(), 1.0 - fidelity);
402            model_selection_scores.insert(channel.to_string(), fidelity);
403        }
404
405        Ok(ProcessComparisons {
406            standard_process_fidelities,
407            process_distances,
408            model_selection_scores,
409        })
410    }
411}