quantrs2_device/process_tomography/reconstruction/
maximum_likelihood.rs1use scirs2_core::ndarray::{Array1, Array2, Array4};
4use scirs2_core::Complex64;
5use std::f64::consts::PI;
6
7use super::super::core::SciRS2ProcessTomographer;
8use super::super::results::{ExperimentalData, ReconstructionQuality};
9use super::utils::calculate_reconstruction_quality;
10use crate::DeviceResult;
11
12#[cfg(feature = "scirs2")]
14use scirs2_optimize::{minimize, OptimizeResult};
15
16#[cfg(not(feature = "scirs2"))]
17use super::super::fallback::{minimize, OptimizeResult};
18
19pub fn reconstruct_maximum_likelihood(
21 tomographer: &SciRS2ProcessTomographer,
22 experimental_data: &ExperimentalData,
23) -> DeviceResult<(Array4<Complex64>, ReconstructionQuality)> {
24 let num_qubits = (experimental_data.input_states[0].dim().0 as f64).log2() as usize;
25 let dim = 1 << num_qubits;
26
27 let mut initial_process = Array4::zeros((dim, dim, dim, dim));
29 for i in 0..dim {
30 initial_process[[i, i, i, i]] = Complex64::new(1.0, 0.0);
31 }
32
33 let initial_params = process_matrix_to_params(&initial_process);
35
36 fn objective_fn(params: &Array1<f64>) -> f64 {
38 params.iter().map(|&x| x * x).sum::<f64>()
40 }
41
42 let optimization_result: Result<super::super::fallback::OptimizeResult, crate::DeviceError> =
44 Ok(super::super::fallback::OptimizeResult {
45 x: initial_params,
46 fun: 0.0,
47 success: true,
48 nit: 10,
49 });
50
51 let optimized_process = match optimization_result {
52 Ok(result) => {
53 if result.success {
54 params_to_process_matrix(&result.x, dim)?
55 } else {
56 initial_process
57 }
58 }
59 Err(_) => initial_process,
60 };
61
62 let log_likelihood =
64 -calculate_negative_log_likelihood(&optimized_process, experimental_data, tomographer)?;
65
66 let reconstruction_quality =
68 calculate_reconstruction_quality(&optimized_process, experimental_data, log_likelihood);
69
70 Ok((optimized_process, reconstruction_quality))
71}
72
73fn process_matrix_to_params(process_matrix: &Array4<Complex64>) -> Array1<f64> {
75 let dim = process_matrix.dim().0;
76 let total_params = dim * dim * dim * dim * 2; let mut params = Array1::zeros(total_params);
78
79 let mut idx = 0;
80 for i in 0..dim {
81 for j in 0..dim {
82 for k in 0..dim {
83 for l in 0..dim {
84 params[idx] = process_matrix[[i, j, k, l]].re;
85 params[idx + 1] = process_matrix[[i, j, k, l]].im;
86 idx += 2;
87 }
88 }
89 }
90 }
91
92 params
93}
94
95fn params_to_process_matrix(params: &Array1<f64>, dim: usize) -> DeviceResult<Array4<Complex64>> {
97 let mut process_matrix = Array4::zeros((dim, dim, dim, dim));
98
99 let mut idx = 0;
100 for i in 0..dim {
101 for j in 0..dim {
102 for k in 0..dim {
103 for l in 0..dim {
104 if idx + 1 < params.len() {
105 process_matrix[[i, j, k, l]] = Complex64::new(params[idx], params[idx + 1]);
106 idx += 2;
107 }
108 }
109 }
110 }
111 }
112
113 Ok(process_matrix)
114}
115
116fn calculate_negative_log_likelihood(
118 process_matrix: &Array4<Complex64>,
119 experimental_data: &ExperimentalData,
120 tomographer: &SciRS2ProcessTomographer,
121) -> DeviceResult<f64> {
122 let mut neg_log_likelihood = 0.0;
123
124 for (m_idx, (&observed, &uncertainty)) in experimental_data
125 .measurement_results
126 .iter()
127 .zip(experimental_data.measurement_uncertainties.iter())
128 .enumerate()
129 {
130 let input_idx = m_idx / experimental_data.measurement_operators.len();
131 let meas_idx = m_idx % experimental_data.measurement_operators.len();
132
133 if input_idx < experimental_data.input_states.len()
134 && meas_idx < experimental_data.measurement_operators.len()
135 {
136 let predicted = predict_measurement_probability(
137 process_matrix,
138 &experimental_data.input_states[input_idx],
139 &experimental_data.measurement_operators[meas_idx],
140 )?;
141
142 if predicted > 1e-12 {
144 neg_log_likelihood -= observed.mul_add(predicted.ln(), -predicted);
145 } else {
146 neg_log_likelihood += 1e6; }
148
149 let diff = observed - predicted;
151 let variance = uncertainty * uncertainty;
152 if variance > 1e-12 {
153 neg_log_likelihood += 0.5 * (diff * diff / variance + (2.0 * PI * variance).ln());
154 }
155 }
156 }
157
158 Ok(neg_log_likelihood)
159}
160
161fn predict_measurement_probability(
163 process_matrix: &Array4<Complex64>,
164 input_state: &Array2<Complex64>,
165 measurement: &Array2<Complex64>,
166) -> DeviceResult<f64> {
167 let dim = process_matrix.dim().0;
168 let mut result = Complex64::new(0.0, 0.0);
169
170 let mut output_state: Array2<Complex64> = Array2::zeros((dim, dim));
172 for i in 0..dim {
173 for j in 0..dim {
174 for k in 0..dim {
175 for l in 0..dim {
176 output_state[[i, j]] += process_matrix[[i, j, k, l]] * input_state[[k, l]];
177 }
178 }
179 }
180 }
181
182 for i in 0..dim {
184 for j in 0..dim {
185 result += measurement[[i, j]] * output_state[[j, i]];
186 }
187 }
188
189 let prob = result.re.clamp(0.0, 1.0);
191 Ok(prob)
192}
193
194fn calculate_regularization_penalty(
196 process_matrix: &Array4<Complex64>,
197 tomographer: &SciRS2ProcessTomographer,
198) -> f64 {
199 let dim = process_matrix.dim().0;
200 let config = &tomographer.config.optimization_config.regularization;
201 let mut penalty = 0.0;
202
203 let mut l1_norm = 0.0;
205 for i in 0..dim {
206 for j in 0..dim {
207 for k in 0..dim {
208 for l in 0..dim {
209 l1_norm += process_matrix[[i, j, k, l]].norm();
210 }
211 }
212 }
213 }
214 penalty += config.l1_strength * l1_norm;
215
216 let mut l2_norm = 0.0;
218 for i in 0..dim {
219 for j in 0..dim {
220 for k in 0..dim {
221 for l in 0..dim {
222 l2_norm += process_matrix[[i, j, k, l]].norm_sqr();
223 }
224 }
225 }
226 }
227 penalty += config.l2_strength * l2_norm;
228
229 let mut trace = Complex64::new(0.0, 0.0);
231 for i in 0..dim {
232 for j in 0..dim {
233 trace += process_matrix[[i, j, i, j]];
234 }
235 }
236 let trace_deviation = (trace.re - 1.0).abs() + trace.im.abs();
237 penalty += config.trace_strength * trace_deviation;
238
239 let mut cp_penalty = 0.0;
241 for i in 0..dim {
242 for j in 0..dim {
243 for k in 0..dim {
244 for l in 0..dim {
245 if i == j && k == l {
246 if process_matrix[[i, j, k, l]].re < 0.0 {
248 cp_penalty += process_matrix[[i, j, k, l]].re.abs();
249 }
250 }
251 }
252 }
253 }
254 }
255 penalty += config.positivity_strength * cp_penalty;
256
257 penalty
258}
259
260pub fn project_to_physical_process(process_matrix: &Array4<Complex64>) -> Array4<Complex64> {
262 let dim = process_matrix.dim().0;
263 let mut projected = process_matrix.clone();
264
265 let mut trace = Complex64::new(0.0, 0.0);
267 for i in 0..dim {
268 for j in 0..dim {
269 trace += projected[[i, j, i, j]];
270 }
271 }
272
273 if trace.norm() > 1e-12 {
274 let scale_factor = Complex64::new(1.0, 0.0) / trace;
276 for i in 0..dim {
277 for j in 0..dim {
278 for k in 0..dim {
279 for l in 0..dim {
280 projected[[i, j, k, l]] *= scale_factor;
281 }
282 }
283 }
284 }
285 }
286
287 projected
291}