1use std::collections::HashMap;
8
9use super::noise::{
10 QecDeferredNoiseEvent, append_qec_pauli_noise_effect, lower_qec_program_to_deferred_circuit,
11 walk_qec_noise_sensitivity,
12};
13use super::{QecNoise, QecOp, QecProgram};
14use crate::error::{PrismError, Result};
15use crate::sim::compiled::xor_words;
16
17#[derive(Debug, Clone, PartialEq)]
20pub struct ErrorMechanism {
21 probability: f64,
22 detectors: Vec<usize>,
23 observables: Vec<usize>,
24}
25
26impl ErrorMechanism {
27 pub fn probability(&self) -> f64 {
28 self.probability
29 }
30
31 pub fn detectors(&self) -> &[usize] {
33 &self.detectors
34 }
35
36 pub fn observables(&self) -> &[usize] {
38 &self.observables
39 }
40}
41
42#[derive(Debug, Clone, PartialEq)]
50pub struct DetectorErrorModel {
51 mechanisms: Vec<ErrorMechanism>,
52 detector_coords: Vec<Vec<f64>>,
53 num_detectors: usize,
54 num_observables: usize,
55}
56
57impl DetectorErrorModel {
58 pub fn mechanisms(&self) -> &[ErrorMechanism] {
59 &self.mechanisms
60 }
61
62 pub fn num_mechanisms(&self) -> usize {
63 self.mechanisms.len()
64 }
65
66 pub fn num_detectors(&self) -> usize {
67 self.num_detectors
68 }
69
70 pub fn num_observables(&self) -> usize {
71 self.num_observables
72 }
73
74 pub fn detector_coords(&self) -> &[Vec<f64>] {
77 &self.detector_coords
78 }
79
80 pub fn decompose_graphlike(&self) -> Result<DetectorErrorModel> {
94 let mut graphlike: Vec<ErrorMechanism> = Vec::new();
95 for mechanism in &self.mechanisms {
96 if mechanism.detectors.len() <= 2 {
97 graphlike.push(mechanism.clone());
98 }
99 }
100
101 for mechanism in &self.mechanisms {
102 if mechanism.detectors.len() <= 2 {
103 continue;
104 }
105 let Some(components) = partition_cover(mechanism, &graphlike) else {
106 return Err(PrismError::InvalidParameter {
107 message: format!(
108 "graphlike decomposition failed: mechanism `{}` has no \
109 cover by graphlike mechanisms",
110 symptom_label(mechanism)
111 ),
112 });
113 };
114 let p = mechanism.probability;
115 for at in components {
116 let prior = graphlike[at].probability;
117 graphlike[at].probability = prior * (1.0 - p) + p * (1.0 - prior);
118 }
119 }
120
121 Ok(DetectorErrorModel {
122 mechanisms: graphlike,
123 detector_coords: self.detector_coords.clone(),
124 num_detectors: self.num_detectors,
125 num_observables: self.num_observables,
126 })
127 }
128
129 pub fn to_text(&self) -> String {
136 let mut out = String::new();
137 for mechanism in &self.mechanisms {
138 out.push_str(&format!("error({})", mechanism.probability));
139 for detector in &mechanism.detectors {
140 out.push_str(&format!(" D{detector}"));
141 }
142 for observable in &mechanism.observables {
143 out.push_str(&format!(" L{observable}"));
144 }
145 out.push('\n');
146 }
147 for (detector, coords) in self.detector_coords.iter().enumerate() {
148 if coords.is_empty() {
149 out.push_str(&format!("detector D{detector}\n"));
150 } else {
151 let coords = coords
152 .iter()
153 .map(f64::to_string)
154 .collect::<Vec<_>>()
155 .join(", ");
156 out.push_str(&format!("detector({coords}) D{detector}\n"));
157 }
158 }
159 for observable in 0..self.num_observables {
160 out.push_str(&format!("logical_observable L{observable}\n"));
161 }
162 out
163 }
164}
165
166impl QecProgram {
167 pub fn detector_error_model(&self) -> Result<DetectorErrorModel> {
205 derive_detector_error_model(self)
206 }
207}
208
209struct FaultUnit {
213 position: usize,
214 branches: Vec<(f64, Vec<u64>)>,
215}
216
217type Symptom = (Vec<usize>, Vec<usize>);
219
220fn derive_detector_error_model(program: &QecProgram) -> Result<DetectorErrorModel> {
221 let detector_rows = program.detector_rows()?;
222 let observable_rows = program.observable_rows()?;
223 let num_detectors = detector_rows.len();
224 let num_observables = observable_rows.len();
225 let m_words = program.num_measurements().div_ceil(64);
226 let detector_masks = pack_record_rows(&detector_rows, m_words);
227 let observable_masks = pack_record_rows(&observable_rows, m_words);
228
229 let deferred = lower_qec_program_to_deferred_circuit(program)?;
230 let mut units: Vec<FaultUnit> = Vec::new();
231 walk_qec_noise_sensitivity(&deferred, |event, x_packed, z_packed| {
232 collect_fault_units(event, x_packed, z_packed, &mut units);
233 })?;
234 units.sort_by_key(|unit| unit.position);
235
236 let mut index: HashMap<Symptom, usize> = HashMap::new();
237 let mut mechanisms: Vec<ErrorMechanism> = Vec::new();
238 for unit in units {
239 for (symptom, probability) in unit_symptoms(&unit, &detector_masks, &observable_masks) {
240 match index.get(&symptom) {
241 Some(&at) => {
242 let prior = mechanisms[at].probability;
243 mechanisms[at].probability =
244 prior * (1.0 - probability) + probability * (1.0 - prior);
245 }
246 None => {
247 index.insert(symptom.clone(), mechanisms.len());
248 let (detectors, observables) = symptom;
249 mechanisms.push(ErrorMechanism {
250 probability,
251 detectors,
252 observables,
253 });
254 }
255 }
256 }
257 }
258
259 Ok(DetectorErrorModel {
260 mechanisms,
261 detector_coords: detector_coordinates(program),
262 num_detectors,
263 num_observables,
264 })
265}
266
267fn collect_fault_units(
268 event: &QecDeferredNoiseEvent,
269 x_packed: &[Vec<u64>],
270 z_packed: &[Vec<u64>],
271 units: &mut Vec<FaultUnit>,
272) {
273 match event.channel {
274 QecNoise::XError(p) => {
275 for &target in &event.targets {
276 units.push(FaultUnit {
277 position: event.position,
278 branches: vec![(p, z_packed[target].clone())],
279 });
280 }
281 }
282 QecNoise::ZError(p) => {
283 for &target in &event.targets {
284 units.push(FaultUnit {
285 position: event.position,
286 branches: vec![(p, x_packed[target].clone())],
287 });
288 }
289 }
290 QecNoise::Depolarize1(p) => {
291 let branch_p = p / 3.0;
292 for &target in &event.targets {
293 let mut y_mask = x_packed[target].clone();
294 xor_words(&mut y_mask, &z_packed[target]);
295 units.push(FaultUnit {
296 position: event.position,
297 branches: vec![
298 (branch_p, z_packed[target].clone()),
299 (branch_p, y_mask),
300 (branch_p, x_packed[target].clone()),
301 ],
302 });
303 }
304 }
305 QecNoise::Depolarize2(p) => {
306 let branch_p = p / 15.0;
307 for pair in event.targets.chunks_exact(2) {
308 let m_words = z_packed[pair[0]].len();
309 let mut branches = Vec::with_capacity(15);
310 for sample in 1..=15 {
311 let mut mask = vec![0u64; m_words];
312 append_qec_pauli_noise_effect(
313 &mut mask,
314 sample / 4,
315 &x_packed[pair[0]],
316 &z_packed[pair[0]],
317 );
318 append_qec_pauli_noise_effect(
319 &mut mask,
320 sample % 4,
321 &x_packed[pair[1]],
322 &z_packed[pair[1]],
323 );
324 branches.push((branch_p, mask));
325 }
326 units.push(FaultUnit {
327 position: event.position,
328 branches,
329 });
330 }
331 }
332 }
333}
334
335fn unit_symptoms(
339 unit: &FaultUnit,
340 detector_masks: &[Vec<u64>],
341 observable_masks: &[Vec<u64>],
342) -> Vec<(Symptom, f64)> {
343 let mut local: Vec<(Symptom, f64)> = Vec::new();
344 for (probability, mask) in &unit.branches {
345 let detectors = flipped_rows(mask, detector_masks);
346 let observables = flipped_rows(mask, observable_masks);
347 if detectors.is_empty() && observables.is_empty() {
348 continue;
349 }
350 let symptom = (detectors, observables);
351 match local.iter_mut().find(|(existing, _)| *existing == symptom) {
352 Some((_, total)) => *total += probability,
353 None => local.push((symptom, *probability)),
354 }
355 }
356 local
357}
358
359fn flipped_rows(mask: &[u64], rows: &[Vec<u64>]) -> Vec<usize> {
360 rows.iter()
361 .enumerate()
362 .filter(|(_, row)| odd_overlap(mask, row))
363 .map(|(row_index, _)| row_index)
364 .collect()
365}
366
367fn odd_overlap(a: &[u64], b: &[u64]) -> bool {
368 a.iter()
369 .zip(b)
370 .map(|(x, y)| (x & y).count_ones())
371 .sum::<u32>()
372 % 2
373 == 1
374}
375
376fn pack_record_rows(rows: &[Vec<usize>], m_words: usize) -> Vec<Vec<u64>> {
379 rows.iter()
380 .map(|row| {
381 let mut mask = vec![0u64; m_words];
382 for &record in row {
383 mask[record / 64] ^= 1u64 << (record % 64);
384 }
385 mask
386 })
387 .collect()
388}
389
390fn partition_cover(mechanism: &ErrorMechanism, graphlike: &[ErrorMechanism]) -> Option<Vec<usize>> {
393 fn search(
394 remaining: &[usize],
395 observables: &[usize],
396 start: usize,
397 graphlike: &[ErrorMechanism],
398 chosen: &mut Vec<usize>,
399 ) -> bool {
400 if remaining.is_empty() {
401 return observables.is_empty();
402 }
403 for at in start..graphlike.len() {
404 let candidate = &graphlike[at];
405 if candidate.detectors.is_empty() || !is_subset(&candidate.detectors, remaining) {
406 continue;
407 }
408 let next_remaining = symmetric_difference(remaining, &candidate.detectors);
409 let next_observables = symmetric_difference(observables, &candidate.observables);
410 chosen.push(at);
411 if search(
412 &next_remaining,
413 &next_observables,
414 at + 1,
415 graphlike,
416 chosen,
417 ) {
418 return true;
419 }
420 chosen.pop();
421 }
422 false
423 }
424
425 let mut chosen = Vec::new();
426 search(
427 &mechanism.detectors,
428 &mechanism.observables,
429 0,
430 graphlike,
431 &mut chosen,
432 )
433 .then_some(chosen)
434}
435
436fn is_subset(a: &[usize], b: &[usize]) -> bool {
438 let mut j = 0;
439 'outer: for &x in a {
440 while j < b.len() {
441 match b[j].cmp(&x) {
442 std::cmp::Ordering::Less => j += 1,
443 std::cmp::Ordering::Equal => {
444 j += 1;
445 continue 'outer;
446 }
447 std::cmp::Ordering::Greater => return false,
448 }
449 }
450 return false;
451 }
452 true
453}
454
455fn symmetric_difference(a: &[usize], b: &[usize]) -> Vec<usize> {
457 let mut out = Vec::with_capacity(a.len() + b.len());
458 let (mut i, mut j) = (0, 0);
459 while i < a.len() && j < b.len() {
460 match a[i].cmp(&b[j]) {
461 std::cmp::Ordering::Less => {
462 out.push(a[i]);
463 i += 1;
464 }
465 std::cmp::Ordering::Greater => {
466 out.push(b[j]);
467 j += 1;
468 }
469 std::cmp::Ordering::Equal => {
470 i += 1;
471 j += 1;
472 }
473 }
474 }
475 out.extend_from_slice(&a[i..]);
476 out.extend_from_slice(&b[j..]);
477 out
478}
479
480pub(super) fn symptom_label(mechanism: &ErrorMechanism) -> String {
481 let mut label = String::new();
482 for detector in &mechanism.detectors {
483 if !label.is_empty() {
484 label.push(' ');
485 }
486 label.push_str(&format!("D{detector}"));
487 }
488 for observable in &mechanism.observables {
489 if !label.is_empty() {
490 label.push(' ');
491 }
492 label.push_str(&format!("L{observable}"));
493 }
494 label
495}
496
497fn detector_coordinates(program: &QecProgram) -> Vec<Vec<f64>> {
498 program
499 .ops()
500 .iter()
501 .filter_map(|op| match op {
502 QecOp::Detector { coords, .. } => Some(coords.clone()),
503 _ => None,
504 })
505 .collect()
506}