1use std::collections::HashMap;
8use std::collections::hash_map::Entry;
9
10use super::DetectorErrorModel;
11use super::dem::symptom_label;
12use crate::error::{PrismError, Result};
13use crate::sim::compiled::{PackedShots, ShotLayout};
14
15pub(super) const BOUNDARY: u32 = u32::MAX;
16const EDGE_NONE: u32 = u32::MAX;
17const VERTEX_NONE: u32 = u32::MAX;
18const GROWTH_EPS: f64 = 1e-9;
19
20#[derive(Debug, Clone)]
31pub struct UnionFindDecoder {
32 num_detectors: usize,
33 num_observables: usize,
34 obs_words: usize,
35 edge_u: Vec<u32>,
36 edge_v: Vec<u32>,
37 edge_weight: Vec<f64>,
38 edge_obs: Vec<u64>,
39 adj_offsets: Vec<u32>,
40 adj_edge: Vec<u32>,
41}
42
43impl UnionFindDecoder {
44 pub fn from_model(model: &DetectorErrorModel) -> Result<Self> {
53 let GraphEdges {
54 num_detectors,
55 num_observables,
56 edge_u,
57 edge_v,
58 edge_p,
59 edge_obs,
60 } = graphlike_edges(model, "union-find")?;
61 let obs_words = num_observables.div_ceil(64);
62 let edge_weight: Vec<f64> = edge_p
63 .iter()
64 .map(|&p| ((1.0 - p) / p).ln().max(0.0))
65 .collect();
66
67 let mut adj_offsets = vec![0u32; num_detectors + 1];
68 for edge in 0..edge_u.len() {
69 adj_offsets[edge_u[edge] as usize + 1] += 1;
70 if edge_v[edge] != BOUNDARY {
71 adj_offsets[edge_v[edge] as usize + 1] += 1;
72 }
73 }
74 for v in 0..num_detectors {
75 adj_offsets[v + 1] += adj_offsets[v];
76 }
77 let mut cursor = adj_offsets.clone();
78 let mut adj_edge = vec![0u32; *adj_offsets.last().unwrap() as usize];
79 for edge in 0..edge_u.len() {
80 let u = edge_u[edge] as usize;
81 adj_edge[cursor[u] as usize] = edge as u32;
82 cursor[u] += 1;
83 if edge_v[edge] != BOUNDARY {
84 let v = edge_v[edge] as usize;
85 adj_edge[cursor[v] as usize] = edge as u32;
86 cursor[v] += 1;
87 }
88 }
89
90 Ok(Self {
91 num_detectors,
92 num_observables,
93 obs_words,
94 edge_u,
95 edge_v,
96 edge_weight,
97 edge_obs,
98 adj_offsets,
99 adj_edge,
100 })
101 }
102
103 pub fn num_detectors(&self) -> usize {
104 self.num_detectors
105 }
106
107 pub fn num_observables(&self) -> usize {
108 self.num_observables
109 }
110
111 pub fn decode_packed(&self, detectors: &PackedShots) -> Result<PackedShots> {
124 decode_batch(self, detectors)
125 }
126
127 pub fn logical_error_rate(
135 &self,
136 detectors: &PackedShots,
137 observables: &PackedShots,
138 ) -> Result<f64> {
139 logical_error_rate(&self.decode_packed(detectors)?, observables)
140 }
141
142 fn decode_shot(
143 &self,
144 row: &[u64],
145 out_row: &mut [u64],
146 s: &mut DecodeScratch,
147 ) -> std::result::Result<(), Stuck> {
148 s.stamp += 1;
149 s.shot_stamp = s.stamp;
150
151 s.defects.clear();
152 for (word_index, &bits) in row.iter().enumerate() {
153 let mut bits = bits;
154 while bits != 0 {
155 s.defects
156 .push((word_index * 64) as u32 + bits.trailing_zeros());
157 bits &= bits - 1;
158 }
159 }
160 if s.defects.is_empty() {
161 return Ok(());
162 }
163
164 s.active.clear();
165 let mut i = 0;
166 while i < s.defects.len() {
167 let defect = s.defects[i];
168 i += 1;
169 s.activate(defect);
170 s.parity[defect as usize] = true;
171 s.defect_stamp[defect as usize] = s.shot_stamp;
172 s.active.push(defect);
173 }
174
175 self.grow_clusters(s)?;
176
177 let mut i = 0;
178 while i < s.defects.len() {
179 let root = s.find(s.defects[i]);
180 i += 1;
181 if s.peeled_stamp[root as usize] == s.shot_stamp {
182 continue;
183 }
184 s.peeled_stamp[root as usize] = s.shot_stamp;
185 self.peel_cluster(root, out_row, s);
186 }
187 Ok(())
188 }
189
190 fn grow_clusters(&self, s: &mut DecodeScratch) -> std::result::Result<(), Stuck> {
195 loop {
196 s.stamp += 1;
197 let round = s.stamp;
198
199 let mut live = 0usize;
200 let mut i = 0;
201 while i < s.active.len() {
202 let root = s.find(s.active[i]);
203 i += 1;
204 if s.seen_stamp[root as usize] == round {
205 continue;
206 }
207 s.seen_stamp[root as usize] = round;
208 if s.parity[root as usize] && !s.boundary[root as usize] {
209 s.active[live] = root;
210 live += 1;
211 }
212 }
213 s.active.truncate(live);
214 if s.active.is_empty() {
215 return Ok(());
216 }
217
218 s.touched.clear();
219 for &root in &s.active {
220 let mut grew = false;
221 let mut lowest = root;
222 let mut v = root;
223 while v != VERTEX_NONE {
224 lowest = lowest.min(v);
225 let begin = self.adj_offsets[v as usize] as usize;
226 let end = self.adj_offsets[v as usize + 1] as usize;
227 for &edge in &self.adj_edge[begin..end] {
228 let e = edge as usize;
229 if s.edge_stamp[e] == s.shot_stamp && s.edge_saturated[e] {
230 continue;
231 }
232 grew = true;
233 if s.touch_stamp[e] == round {
234 s.touch_count[e] += 1;
235 } else {
236 s.touch_stamp[e] = round;
237 s.touch_count[e] = 1;
238 s.touched.push(edge);
239 }
240 }
241 v = s.list_next[v as usize];
242 }
243 if !grew {
244 return Err(Stuck { detector: lowest });
245 }
246 }
247
248 let mut delta = f64::INFINITY;
249 for &edge in &s.touched {
250 let e = edge as usize;
251 let growth = if s.edge_stamp[e] == s.shot_stamp {
252 s.edge_growth[e]
253 } else {
254 0.0
255 };
256 let step = (self.edge_weight[e] - growth) / f64::from(s.touch_count[e]);
257 if step < delta {
258 delta = step;
259 }
260 }
261
262 s.fused.clear();
263 for &edge in &s.touched {
264 let e = edge as usize;
265 if s.edge_stamp[e] != s.shot_stamp {
266 s.edge_stamp[e] = s.shot_stamp;
267 s.edge_growth[e] = 0.0;
268 s.edge_saturated[e] = false;
269 }
270 s.edge_growth[e] += f64::from(s.touch_count[e]) * delta;
271 if s.edge_growth[e] + GROWTH_EPS >= self.edge_weight[e] {
272 s.fused.push(e as u32);
273 }
274 }
275 s.fused.sort_unstable();
276 let mut i = 0;
277 while i < s.fused.len() {
278 let edge = s.fused[i];
279 i += 1;
280 s.edge_saturated[edge as usize] = true;
281 let u = self.edge_u[edge as usize];
282 let v = self.edge_v[edge as usize];
283 s.activate(u);
284 if v == BOUNDARY {
285 let root = s.find(u);
286 s.boundary[root as usize] = true;
287 s.boundary_edge[root as usize] = s.boundary_edge[root as usize].min(edge);
288 } else {
289 s.activate(v);
290 let ru = s.find(u);
291 let rv = s.find(v);
292 if ru != rv {
293 s.union(ru, rv);
294 }
295 }
296 }
297 }
298 }
299
300 fn peel_cluster(&self, root: u32, out_row: &mut [u64], s: &mut DecodeScratch) {
304 let start = if s.boundary[root as usize] {
305 self.edge_u[s.boundary_edge[root as usize] as usize]
306 } else {
307 let mut lowest = root;
308 let mut v = root;
309 while v != VERTEX_NONE {
310 lowest = lowest.min(v);
311 v = s.list_next[v as usize];
312 }
313 lowest
314 };
315
316 s.order.clear();
317 s.stack.clear();
318 s.dfs_stamp[start as usize] = s.shot_stamp;
319 s.stack.push(start);
320 while let Some(v) = s.stack.pop() {
321 let begin = self.adj_offsets[v as usize] as usize;
322 let end = self.adj_offsets[v as usize + 1] as usize;
323 for &edge in &self.adj_edge[begin..end] {
324 let e = edge as usize;
325 if s.edge_stamp[e] != s.shot_stamp || !s.edge_saturated[e] {
326 continue;
327 }
328 if self.edge_v[e] == BOUNDARY {
329 continue;
330 }
331 let other = if self.edge_u[e] == v {
332 self.edge_v[e]
333 } else {
334 self.edge_u[e]
335 };
336 if s.dfs_stamp[other as usize] == s.shot_stamp {
337 continue;
338 }
339 s.dfs_stamp[other as usize] = s.shot_stamp;
340 s.order.push((other, edge, v));
341 s.stack.push(other);
342 }
343 }
344
345 for &(vertex, edge, parent) in s.order.iter().rev() {
346 if s.defect_stamp[vertex as usize] != s.shot_stamp {
347 continue;
348 }
349 s.defect_stamp[vertex as usize] = 0;
350 if s.defect_stamp[parent as usize] == s.shot_stamp {
351 s.defect_stamp[parent as usize] = 0;
352 } else {
353 s.defect_stamp[parent as usize] = s.shot_stamp;
354 }
355 self.xor_edge_observables(edge, out_row);
356 }
357
358 if s.defect_stamp[start as usize] == s.shot_stamp {
359 s.defect_stamp[start as usize] = 0;
360 debug_assert!(s.boundary[root as usize]);
361 self.xor_edge_observables(s.boundary_edge[root as usize], out_row);
362 }
363 }
364
365 #[inline]
366 fn xor_edge_observables(&self, edge: u32, out_row: &mut [u64]) {
367 let base = edge as usize * self.obs_words;
368 for (word, mask) in out_row
369 .iter_mut()
370 .zip(&self.edge_obs[base..base + self.obs_words])
371 {
372 *word ^= mask;
373 }
374 }
375}
376
377pub(super) struct GraphEdges {
381 pub(super) num_detectors: usize,
382 pub(super) num_observables: usize,
383 pub(super) edge_u: Vec<u32>,
384 pub(super) edge_v: Vec<u32>,
385 pub(super) edge_p: Vec<f64>,
386 pub(super) edge_obs: Vec<u64>,
387}
388
389pub(super) fn graphlike_edges(model: &DetectorErrorModel, decoding: &str) -> Result<GraphEdges> {
393 if model.num_detectors() >= BOUNDARY as usize {
394 return Err(PrismError::InvalidParameter {
395 message: format!(
396 "{} detectors exceed the decoder's index range",
397 model.num_detectors()
398 ),
399 });
400 }
401 let obs_words = model.num_observables().div_ceil(64);
402 let mut edge_u: Vec<u32> = Vec::new();
403 let mut edge_v: Vec<u32> = Vec::new();
404 let mut edge_p: Vec<f64> = Vec::new();
405 let mut edge_obs_rows: Vec<&[usize]> = Vec::new();
406 let mut slots: HashMap<(u32, u32), u32> = HashMap::new();
407 for mechanism in model.mechanisms() {
408 let p = mechanism.probability();
409 if !(0.0..1.0).contains(&p) {
410 return Err(PrismError::InvalidParameter {
411 message: format!(
412 "mechanism `{}` has probability {p}, outside [0, 1)",
413 symptom_label(mechanism)
414 ),
415 });
416 }
417 if p == 0.0 {
418 continue;
419 }
420 let endpoints = match *mechanism.detectors() {
421 [] => continue,
422 [d] => (d as u32, BOUNDARY),
423 [d0, d1] => (d0 as u32, d1 as u32),
424 _ => {
425 return Err(PrismError::InvalidParameter {
426 message: format!(
427 "mechanism `{}` flips {} detectors; {decoding} decoding needs a graphlike model, apply `decompose_graphlike` first",
428 symptom_label(mechanism),
429 mechanism.detectors().len()
430 ),
431 });
432 }
433 };
434 match slots.entry(endpoints) {
435 Entry::Occupied(slot) => {
436 let at = *slot.get() as usize;
437 if p > edge_p[at] {
438 edge_p[at] = p;
439 edge_obs_rows[at] = mechanism.observables();
440 }
441 }
442 Entry::Vacant(slot) => {
443 slot.insert(edge_u.len() as u32);
444 edge_u.push(endpoints.0);
445 edge_v.push(endpoints.1);
446 edge_p.push(p);
447 edge_obs_rows.push(mechanism.observables());
448 }
449 }
450 }
451
452 let mut edge_obs = vec![0u64; edge_u.len() * obs_words];
453 for (edge, row) in edge_obs_rows.iter().enumerate() {
454 for &observable in *row {
455 edge_obs[edge * obs_words + observable / 64] |= 1u64 << (observable % 64);
456 }
457 }
458 Ok(GraphEdges {
459 num_detectors: model.num_detectors(),
460 num_observables: model.num_observables(),
461 edge_u,
462 edge_v,
463 edge_p,
464 edge_obs,
465 })
466}
467
468impl ShotDecoder for UnionFindDecoder {
469 type Scratch = DecodeScratch;
470 type Failure = Stuck;
471
472 fn num_detectors(&self) -> usize {
473 self.num_detectors
474 }
475
476 fn num_observables(&self) -> usize {
477 self.num_observables
478 }
479
480 fn scratch(&self) -> DecodeScratch {
481 DecodeScratch::new(self)
482 }
483
484 fn decode_shot(
485 &self,
486 row: &[u64],
487 out_row: &mut [u64],
488 scratch: &mut DecodeScratch,
489 ) -> std::result::Result<(), Stuck> {
490 UnionFindDecoder::decode_shot(self, row, out_row, scratch)
491 }
492
493 fn failure_error(failure: Stuck, shot: usize) -> PrismError {
494 failure.into_error(shot)
495 }
496}
497
498pub(super) trait ShotDecoder: Sync {
502 type Scratch;
503 type Failure: Send;
504 #[cfg(feature = "parallel")]
506 const PARALLEL_SHOT_THRESHOLD: usize = 1024;
507 #[cfg(feature = "parallel")]
509 const SHOT_CHUNK: usize = 256;
510
511 fn num_detectors(&self) -> usize;
512 fn num_observables(&self) -> usize;
513 fn scratch(&self) -> Self::Scratch;
514 fn decode_shot(
515 &self,
516 row: &[u64],
517 out_row: &mut [u64],
518 scratch: &mut Self::Scratch,
519 ) -> std::result::Result<(), Self::Failure>;
520 fn failure_error(failure: Self::Failure, shot: usize) -> PrismError;
521}
522
523pub(super) fn decode_batch<D: ShotDecoder>(
524 decoder: &D,
525 detectors: &PackedShots,
526) -> Result<PackedShots> {
527 let num_detectors = decoder.num_detectors();
528 let num_observables = decoder.num_observables();
529 if detectors.num_measurements() != num_detectors {
530 return Err(PrismError::InvalidParameter {
531 message: format!(
532 "detector shots carry {} measurements, the model has {} detectors",
533 detectors.num_measurements(),
534 num_detectors
535 ),
536 });
537 }
538 let num_shots = detectors.num_shots();
539 let m_words = num_detectors.div_ceil(64);
540 let transposed;
541 let rows: &[u64] = match detectors.layout() {
542 ShotLayout::ShotMajor => detectors.raw_data(),
543 ShotLayout::MeasMajor => {
544 transposed = detectors.clone().into_shot_major_data();
545 &transposed
546 }
547 };
548 let out_words = num_observables.div_ceil(64);
549 let mut out = vec![0u64; num_shots * out_words];
550
551 #[cfg(feature = "parallel")]
552 if num_shots >= D::PARALLEL_SHOT_THRESHOLD && out_words > 0 {
553 use rayon::prelude::*;
554 let failure = out
555 .par_chunks_mut(D::SHOT_CHUNK * out_words)
556 .enumerate()
557 .map_init(
558 || decoder.scratch(),
559 |scratch, (chunk, chunk_out)| {
560 for (offset, shot_out) in chunk_out.chunks_mut(out_words).enumerate() {
561 let shot = chunk * D::SHOT_CHUNK + offset;
562 let row = &rows[shot * m_words..(shot + 1) * m_words];
563 if let Err(failure) = decoder.decode_shot(row, shot_out, scratch) {
564 return Some((shot, failure));
565 }
566 }
567 None
568 },
569 )
570 .reduce(
571 || None,
572 |a, b| match (a, b) {
573 (Some(a), Some(b)) => Some(if a.0 <= b.0 { a } else { b }),
574 (a, b) => a.or(b),
575 },
576 );
577 if let Some((shot, failure)) = failure {
578 return Err(D::failure_error(failure, shot));
579 }
580 return Ok(PackedShots::from_shot_major(
581 out,
582 num_shots,
583 num_observables,
584 ));
585 }
586
587 let mut scratch = decoder.scratch();
588 for shot in 0..num_shots {
589 let row = &rows[shot * m_words..(shot + 1) * m_words];
590 let shot_out = &mut out[shot * out_words..(shot + 1) * out_words];
591 decoder
592 .decode_shot(row, shot_out, &mut scratch)
593 .map_err(|failure| D::failure_error(failure, shot))?;
594 }
595 Ok(PackedShots::from_shot_major(
596 out,
597 num_shots,
598 num_observables,
599 ))
600}
601
602pub(super) fn logical_error_rate(
603 predicted: &PackedShots,
604 observables: &PackedShots,
605) -> Result<f64> {
606 if observables.num_shots() != predicted.num_shots()
607 || observables.num_measurements() != predicted.num_measurements()
608 {
609 return Err(PrismError::InvalidParameter {
610 message: format!(
611 "observable records hold {} shots of {} observables, the prediction {} shots of {}",
612 observables.num_shots(),
613 observables.num_measurements(),
614 predicted.num_shots(),
615 predicted.num_measurements()
616 ),
617 });
618 }
619 let num_shots = predicted.num_shots();
620 if num_shots == 0 {
621 return Ok(0.0);
622 }
623 let words = predicted.num_measurements().div_ceil(64);
624 if words == 0 {
625 return Ok(0.0);
626 }
627 let transposed;
628 let actual: &[u64] = match observables.layout() {
629 ShotLayout::ShotMajor => observables.raw_data(),
630 ShotLayout::MeasMajor => {
631 transposed = observables.clone().into_shot_major_data();
632 &transposed
633 }
634 };
635 let tail = match predicted.num_measurements() % 64 {
636 0 => u64::MAX,
637 bits => (1u64 << bits) - 1,
638 };
639 let failures = predicted
640 .raw_data()
641 .chunks_exact(words)
642 .zip(actual.chunks_exact(words))
643 .filter(|(p, a)| {
644 let last = words - 1;
645 p[..last] != a[..last] || (p[last] ^ a[last]) & tail != 0
646 })
647 .count();
648 Ok(failures as f64 / num_shots as f64)
649}
650
651pub(super) struct Stuck {
652 pub(super) detector: u32,
653}
654
655impl Stuck {
656 pub(super) fn into_error(self, shot: usize) -> PrismError {
657 PrismError::InvalidParameter {
658 message: format!(
659 "shot {shot}: the detector component containing D{} has odd syndrome parity but no boundary edge, so the syndrome is impossible under the model",
660 self.detector
661 ),
662 }
663 }
664}
665
666pub(super) struct DecodeScratch {
670 stamp: u64,
671 shot_stamp: u64,
672 parent: Vec<u32>,
673 size: Vec<u32>,
674 parity: Vec<bool>,
675 boundary: Vec<bool>,
676 boundary_edge: Vec<u32>,
677 list_tail: Vec<u32>,
678 list_next: Vec<u32>,
679 vertex_stamp: Vec<u64>,
680 seen_stamp: Vec<u64>,
681 defect_stamp: Vec<u64>,
682 dfs_stamp: Vec<u64>,
683 peeled_stamp: Vec<u64>,
684 edge_stamp: Vec<u64>,
685 edge_growth: Vec<f64>,
686 edge_saturated: Vec<bool>,
687 touch_stamp: Vec<u64>,
688 touch_count: Vec<u8>,
689 defects: Vec<u32>,
690 active: Vec<u32>,
691 touched: Vec<u32>,
692 fused: Vec<u32>,
693 stack: Vec<u32>,
694 order: Vec<(u32, u32, u32)>,
695}
696
697impl DecodeScratch {
698 fn new(decoder: &UnionFindDecoder) -> Self {
699 let vertices = decoder.num_detectors;
700 let edges = decoder.edge_u.len();
701 Self {
702 stamp: 0,
703 shot_stamp: 0,
704 parent: vec![0; vertices],
705 size: vec![0; vertices],
706 parity: vec![false; vertices],
707 boundary: vec![false; vertices],
708 boundary_edge: vec![0; vertices],
709 list_tail: vec![0; vertices],
710 list_next: vec![0; vertices],
711 vertex_stamp: vec![0; vertices],
712 seen_stamp: vec![0; vertices],
713 defect_stamp: vec![0; vertices],
714 dfs_stamp: vec![0; vertices],
715 peeled_stamp: vec![0; vertices],
716 edge_stamp: vec![0; edges],
717 edge_growth: vec![0.0; edges],
718 edge_saturated: vec![false; edges],
719 touch_stamp: vec![0; edges],
720 touch_count: vec![0; edges],
721 defects: Vec::new(),
722 active: Vec::new(),
723 touched: Vec::new(),
724 fused: Vec::new(),
725 stack: Vec::new(),
726 order: Vec::new(),
727 }
728 }
729
730 fn activate(&mut self, v: u32) {
731 let at = v as usize;
732 if self.vertex_stamp[at] == self.shot_stamp {
733 return;
734 }
735 self.vertex_stamp[at] = self.shot_stamp;
736 self.parent[at] = v;
737 self.size[at] = 1;
738 self.parity[at] = false;
739 self.boundary[at] = false;
740 self.boundary_edge[at] = EDGE_NONE;
741 self.list_tail[at] = v;
742 self.list_next[at] = VERTEX_NONE;
743 }
744
745 fn find(&mut self, mut v: u32) -> u32 {
746 while self.parent[v as usize] != v {
747 let grand = self.parent[self.parent[v as usize] as usize];
748 self.parent[v as usize] = grand;
749 v = grand;
750 }
751 v
752 }
753
754 fn union(&mut self, a: u32, b: u32) {
755 let (big, small) = if self.size[a as usize] > self.size[b as usize]
756 || (self.size[a as usize] == self.size[b as usize] && a < b)
757 {
758 (a, b)
759 } else {
760 (b, a)
761 };
762 let (big_at, small_at) = (big as usize, small as usize);
763 self.parent[small_at] = big;
764 self.size[big_at] += self.size[small_at];
765 self.parity[big_at] ^= self.parity[small_at];
766 self.boundary[big_at] |= self.boundary[small_at];
767 self.boundary_edge[big_at] = self.boundary_edge[big_at].min(self.boundary_edge[small_at]);
768 self.list_next[self.list_tail[big_at] as usize] = small;
769 self.list_tail[big_at] = self.list_tail[small_at];
770 }
771}