fusion_blossom/
example_codes.rs

1//! Example Decoding
2//!
3//! This module contains several abstract decoding graph and it's randomized simulator utilities.
4//! This helps to debug, but it doesn't corresponds to real noise model, nor it's capable of simulating circuit-level noise model.
5//! For complex noise model and simulator functionality, please see <https://github.com/yuewuo/QEC-Playground>
6//!
7//! Note that these examples are not optimized for cache for simplicity.
8//! To maximize code efficiency, user should design how to group vertices such that memory speed is constant for arbitrary large code distance.
9//!
10
11use super::pointers::*;
12use super::util::*;
13use super::visualize::*;
14use crate::derivative::Derivative;
15use crate::rand_xoshiro::rand_core::SeedableRng;
16use crate::rayon::prelude::*;
17use crate::serde_json;
18#[cfg(feature = "python_binding")]
19use pyo3::prelude::*;
20use std::collections::HashMap;
21use std::fs::File;
22use std::io::{self, BufRead};
23
24/// Vertex corresponds to a stabilizer measurement bit
25#[derive(Derivative, Clone)]
26#[derivative(Debug)]
27#[cfg_attr(feature = "python_binding", cfg_eval)]
28#[cfg_attr(feature = "python_binding", pyclass)]
29pub struct CodeVertex {
30    /// position helps to visualize
31    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
32    pub position: VisualizePosition,
33    /// neighbor edges helps to set find individual edge
34    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
35    pub neighbor_edges: Vec<EdgeIndex>,
36    /// virtual vertex won't report measurement results
37    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
38    pub is_virtual: bool,
39    /// whether it's a defect, note that virtual nodes should NOT be defects
40    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
41    pub is_defect: bool,
42}
43
44#[cfg_attr(feature = "python_binding", cfg_eval)]
45#[cfg_attr(feature = "python_binding", pymethods)]
46impl CodeVertex {
47    #[cfg(feature = "python_binding")]
48    fn __repr__(&self) -> String {
49        format!("{:?}", self)
50    }
51}
52
53/// Edge flips the measurement result of two vertices
54#[derive(Derivative, Clone)]
55#[derivative(Debug)]
56#[cfg_attr(feature = "python_binding", cfg_eval)]
57#[cfg_attr(feature = "python_binding", pyclass)]
58pub struct CodeEdge {
59    /// the two vertices incident to this edge
60    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
61    pub vertices: (VertexIndex, VertexIndex),
62    /// probability of flipping the results of these two vertices; do not set p to 0 to remove edge: if desired, create a new code type
63    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
64    pub p: f64,
65    /// probability of having a reported event of error on this edge
66    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
67    pub pe: f64,
68    /// the integer weight of this edge
69    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
70    pub half_weight: Weight,
71    /// whether this edge is erased
72    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
73    pub is_erasure: bool,
74}
75
76#[cfg_attr(feature = "python_binding", cfg_eval)]
77#[cfg_attr(feature = "python_binding", pymethods)]
78impl CodeEdge {
79    #[cfg_attr(feature = "python_binding", new)]
80    pub fn new(a: VertexIndex, b: VertexIndex) -> Self {
81        Self {
82            vertices: (a, b),
83            p: 0.,
84            pe: 0.,
85            half_weight: 0,
86            is_erasure: false,
87        }
88    }
89    #[cfg(feature = "python_binding")]
90    fn __repr__(&self) -> String {
91        format!("{:?}", self)
92    }
93}
94
95/// default function for computing (pre-scaled) weight from probability
96#[cfg_attr(feature = "python_binding", pyfunction)]
97pub fn weight_of_p(p: f64) -> f64 {
98    assert!((0. ..=0.5).contains(&p), "p must be a reasonable value between 0 and 50%");
99    ((1. - p) / p).ln()
100}
101
102pub trait ExampleCode {
103    /// get mutable references to vertices and edges
104    fn vertices_edges(&mut self) -> (&mut Vec<CodeVertex>, &mut Vec<CodeEdge>);
105    fn immutable_vertices_edges(&self) -> (&Vec<CodeVertex>, &Vec<CodeEdge>);
106
107    /// get the number of vertices
108    fn vertex_num(&self) -> VertexNum {
109        self.immutable_vertices_edges().0.len() as VertexNum
110    }
111
112    /// generic method that automatically computes integer weights from probabilities,
113    /// scales such that the maximum integer weight is 10000 and the minimum is 1
114    fn compute_weights(&mut self, max_half_weight: Weight) {
115        let (_vertices, edges) = self.vertices_edges();
116        let mut max_weight = 0.;
117        for edge in edges.iter() {
118            let weight = weight_of_p(edge.p);
119            if weight > max_weight {
120                max_weight = weight;
121            }
122        }
123        assert!(max_weight > 0., "max weight is not expected to be 0.");
124        // scale all weights but set the smallest to 1
125        for edge in edges.iter_mut() {
126            let weight = weight_of_p(edge.p);
127            let half_weight: Weight = ((max_half_weight as f64) * weight / max_weight).round() as Weight;
128            edge.half_weight = if half_weight == 0 { 1 } else { half_weight }; // weight is required to be even
129        }
130    }
131
132    /// sanity check to avoid duplicate edges that are hard to debug
133    fn sanity_check(&self) -> Result<(), String> {
134        let (vertices, edges) = self.immutable_vertices_edges();
135        // check the graph is reasonable
136        if vertices.is_empty() || edges.is_empty() {
137            return Err("empty graph".to_string());
138        }
139        // check duplicated edges
140        let mut existing_edges = HashMap::<(VertexIndex, VertexIndex), EdgeIndex>::with_capacity(edges.len() * 2);
141        for (edge_idx, edge) in edges.iter().enumerate() {
142            let (v1, v2) = edge.vertices;
143            let unique_edge = if v1 < v2 { (v1, v2) } else { (v2, v1) };
144            if existing_edges.contains_key(&unique_edge) {
145                let previous_idx = existing_edges[&unique_edge];
146                return Err(format!(
147                    "duplicate edge {} and {} with incident vertices {} and {}",
148                    previous_idx, edge_idx, v1, v2
149                ));
150            }
151            existing_edges.insert(unique_edge, edge_idx as EdgeIndex);
152        }
153        // check duplicated referenced edge from each vertex
154        for (vertex_idx, vertex) in vertices.iter().enumerate() {
155            let mut existing_edges = HashMap::<EdgeIndex, ()>::new();
156            if vertex.neighbor_edges.is_empty() {
157                return Err(format!("vertex {} do not have any neighbor edges", vertex_idx));
158            }
159            for edge_idx in vertex.neighbor_edges.iter() {
160                if existing_edges.contains_key(edge_idx) {
161                    return Err(format!("duplicate referred edge {} from vertex {}", edge_idx, vertex_idx));
162                }
163                existing_edges.insert(*edge_idx, ());
164            }
165        }
166        Ok(())
167    }
168
169    /// set probability of all edges; user can set individual probabilities
170    fn set_probability(&mut self, p: f64) {
171        let (_vertices, edges) = self.vertices_edges();
172        for edge in edges.iter_mut() {
173            edge.p = p;
174        }
175    }
176
177    /// set erasure probability of all edges; user can set individual probabilities
178    fn set_erasure_probability(&mut self, pe: f64) {
179        let (_vertices, edges) = self.vertices_edges();
180        for edge in edges.iter_mut() {
181            edge.pe = pe;
182        }
183    }
184
185    /// automatically create vertices given edges
186    #[allow(clippy::unnecessary_cast)]
187    fn fill_vertices(&mut self, vertex_num: VertexNum) {
188        let (vertices, edges) = self.vertices_edges();
189        vertices.clear();
190        vertices.reserve(vertex_num as usize);
191        for _ in 0..vertex_num {
192            vertices.push(CodeVertex {
193                position: VisualizePosition::new(0., 0., 0.),
194                neighbor_edges: Vec::new(),
195                is_virtual: false,
196                is_defect: false,
197            });
198        }
199        for (edge_idx, edge) in edges.iter().enumerate() {
200            let vertex_1 = &mut vertices[edge.vertices.0 as usize];
201            vertex_1.neighbor_edges.push(edge_idx as EdgeIndex);
202            let vertex_2 = &mut vertices[edge.vertices.1 as usize];
203            vertex_2.neighbor_edges.push(edge_idx as EdgeIndex);
204        }
205    }
206
207    /// gather all positions of vertices
208    fn get_positions(&self) -> Vec<VisualizePosition> {
209        let (vertices, _edges) = self.immutable_vertices_edges();
210        let mut positions = Vec::with_capacity(vertices.len());
211        for vertex in vertices.iter() {
212            positions.push(vertex.position.clone());
213        }
214        positions
215    }
216
217    /// generate standard interface to instantiate Fusion blossom solver
218    fn get_initializer(&self) -> SolverInitializer {
219        let (vertices, edges) = self.immutable_vertices_edges();
220        let vertex_num = vertices.len() as VertexIndex;
221        let mut weighted_edges = Vec::with_capacity(edges.len());
222        for edge in edges.iter() {
223            weighted_edges.push((edge.vertices.0, edge.vertices.1, edge.half_weight * 2));
224        }
225        let mut virtual_vertices = Vec::new();
226        for (vertex_idx, vertex) in vertices.iter().enumerate() {
227            if vertex.is_virtual {
228                virtual_vertices.push(vertex_idx as VertexIndex);
229            }
230        }
231        SolverInitializer {
232            vertex_num,
233            weighted_edges,
234            virtual_vertices,
235        }
236    }
237
238    /// set defect vertices (non-trivial measurement result in case of single round of measurement,
239    /// or different result from the previous round in case of multiple rounds of measurement)
240    #[allow(clippy::unnecessary_cast)]
241    fn set_defect_vertices(&mut self, defect_vertices: &[VertexIndex]) {
242        let (vertices, _edges) = self.vertices_edges();
243        for vertex in vertices.iter_mut() {
244            vertex.is_defect = false;
245        }
246        for vertex_idx in defect_vertices.iter() {
247            let vertex = &mut vertices[*vertex_idx as usize];
248            vertex.is_defect = true;
249        }
250    }
251
252    /// set erasure edges
253    #[allow(clippy::unnecessary_cast)]
254    fn set_erasures(&mut self, erasures: &[EdgeIndex]) {
255        let (_vertices, edges) = self.vertices_edges();
256        for edge in edges.iter_mut() {
257            edge.is_erasure = false;
258        }
259        for edge_idx in erasures.iter() {
260            let edge = &mut edges[*edge_idx as usize];
261            edge.is_erasure = true;
262        }
263    }
264
265    /// set syndrome
266    fn set_syndrome(&mut self, syndrome_pattern: &SyndromePattern) {
267        self.set_defect_vertices(&syndrome_pattern.defect_vertices);
268        self.set_erasures(&syndrome_pattern.erasures);
269    }
270
271    /// get current defect vertices
272    fn get_defect_vertices(&self) -> Vec<VertexIndex> {
273        let (vertices, _edges) = self.immutable_vertices_edges();
274        let mut syndrome = Vec::new();
275        for (vertex_idx, vertex) in vertices.iter().enumerate() {
276            if vertex.is_defect {
277                syndrome.push(vertex_idx as VertexIndex);
278            }
279        }
280        syndrome
281    }
282
283    /// get current erasure edges
284    fn get_erasures(&self) -> Vec<EdgeIndex> {
285        let (_vertices, edges) = self.immutable_vertices_edges();
286        let mut erasures = Vec::new();
287        for (edge_idx, edge) in edges.iter().enumerate() {
288            if edge.is_erasure {
289                erasures.push(edge_idx as EdgeIndex);
290            }
291        }
292        erasures
293    }
294
295    /// get current syndrome
296    fn get_syndrome(&self) -> SyndromePattern {
297        SyndromePattern::new(self.get_defect_vertices(), self.get_erasures())
298    }
299
300    /// generate random errors based on the edge probabilities and a seed for pseudo number generator
301    #[allow(clippy::unnecessary_cast)]
302    fn generate_random_errors(&mut self, seed: u64) -> SyndromePattern {
303        let mut rng = DeterministicRng::seed_from_u64(seed);
304        let (vertices, edges) = self.vertices_edges();
305        for vertex in vertices.iter_mut() {
306            vertex.is_defect = false;
307        }
308        for edge in edges.iter_mut() {
309            let p = if rng.next_f64() < edge.pe {
310                edge.is_erasure = true;
311                0.5 // when erasure happens, there are 50% chance of error
312            } else {
313                edge.is_erasure = false;
314                edge.p
315            };
316            if rng.next_f64() < p {
317                let (v1, v2) = edge.vertices;
318                let vertex_1 = &mut vertices[v1 as usize];
319                if !vertex_1.is_virtual {
320                    vertex_1.is_defect = !vertex_1.is_defect;
321                }
322                let vertex_2 = &mut vertices[v2 as usize];
323                if !vertex_2.is_virtual {
324                    vertex_2.is_defect = !vertex_2.is_defect;
325                }
326            }
327        }
328        self.get_syndrome()
329    }
330
331    #[allow(clippy::unnecessary_cast)]
332    fn generate_errors(&mut self, edge_indices: &[EdgeIndex]) -> SyndromePattern {
333        let (vertices, edges) = self.vertices_edges();
334        for &edge_index in edge_indices {
335            let edge = &mut edges.get_mut(edge_index as usize).unwrap();
336            let (v1, v2) = edge.vertices;
337            let vertex_1 = &mut vertices[v1 as usize];
338            if !vertex_1.is_virtual {
339                vertex_1.is_defect = !vertex_1.is_defect;
340            }
341            let vertex_2 = &mut vertices[v2 as usize];
342            if !vertex_2.is_virtual {
343                vertex_2.is_defect = !vertex_2.is_defect;
344            }
345        }
346        self.get_syndrome()
347    }
348
349    fn clear_errors(&mut self) {
350        let (vertices, edges) = self.vertices_edges();
351        for vertex in vertices.iter_mut() {
352            vertex.is_defect = false;
353        }
354        for edge in edges.iter_mut() {
355            edge.is_erasure = true;
356        }
357    }
358
359    fn is_virtual(&self, vertex_idx: usize) -> bool {
360        let (vertices, _edges) = self.immutable_vertices_edges();
361        vertices[vertex_idx].is_virtual
362    }
363
364    fn is_defect(&self, vertex_idx: usize) -> bool {
365        let (vertices, _edges) = self.immutable_vertices_edges();
366        vertices[vertex_idx].is_defect
367    }
368
369    /// reorder the vertices such that new vertices (the indices of the old order) is sequential
370    #[allow(clippy::unnecessary_cast)]
371    fn reorder_vertices(&mut self, sequential_vertices: &[VertexIndex]) {
372        let (vertices, edges) = self.vertices_edges();
373        assert_eq!(vertices.len(), sequential_vertices.len(), "amount of vertices must be same");
374        let old_to_new = build_old_to_new(sequential_vertices);
375        // change the vertices numbering
376        *vertices = (0..vertices.len())
377            .map(|new_index| vertices[sequential_vertices[new_index] as usize].clone())
378            .collect();
379        for edge in edges.iter_mut() {
380            let (old_left, old_right) = edge.vertices;
381            edge.vertices = (
382                old_to_new[old_left as usize].unwrap(),
383                old_to_new[old_right as usize].unwrap(),
384            );
385        }
386    }
387}
388
389#[cfg(feature = "python_binding")]
390use rand::{thread_rng, Rng};
391
392#[cfg(feature = "python_binding")]
393macro_rules! bind_trait_example_code {
394    ($struct_name:ident) => {
395        #[pymethods]
396        impl $struct_name {
397            fn __repr__(&self) -> String {
398                format!("{:?}", self)
399            }
400            #[pyo3(name = "vertex_num")]
401            fn trait_vertex_num(&self) -> VertexNum {
402                self.vertex_num()
403            }
404            #[pyo3(name = "compute_weights")]
405            fn trait_compute_weights(&mut self, max_half_weight: Weight) {
406                self.compute_weights(max_half_weight)
407            }
408            #[pyo3(name = "sanity_check")]
409            fn trait_sanity_check(&self) -> Option<String> {
410                self.sanity_check().err()
411            }
412            #[pyo3(name = "set_probability")]
413            fn trait_set_probability(&mut self, p: f64) {
414                self.set_probability(p)
415            }
416            #[pyo3(name = "set_erasure_probability")]
417            fn trait_set_erasure_probability(&mut self, p: f64) {
418                self.set_erasure_probability(p)
419            }
420            #[pyo3(name = "fill_vertices")]
421            fn trait_fill_vertices(&mut self, vertex_num: VertexNum) {
422                self.fill_vertices(vertex_num)
423            }
424            #[pyo3(name = "get_positions")]
425            fn trait_get_positions(&self) -> Vec<VisualizePosition> {
426                self.get_positions()
427            }
428            #[pyo3(name = "get_initializer")]
429            fn trait_get_initializer(&self) -> SolverInitializer {
430                self.get_initializer()
431            }
432            #[pyo3(name = "set_defect_vertices")]
433            fn trait_set_defect_vertices(&mut self, defect_vertices: Vec<VertexIndex>) {
434                self.set_defect_vertices(&defect_vertices)
435            }
436            #[pyo3(name = "set_erasures")]
437            fn trait_set_erasures(&mut self, erasures: Vec<EdgeIndex>) {
438                self.set_erasures(&erasures)
439            }
440            #[pyo3(name = "set_syndrome")]
441            fn trait_set_syndrome(&mut self, syndrome_pattern: &SyndromePattern) {
442                self.set_syndrome(syndrome_pattern)
443            }
444            #[pyo3(name = "get_defect_vertices")]
445            fn trait_get_defect_vertices(&self) -> Vec<VertexIndex> {
446                self.get_defect_vertices()
447            }
448            #[pyo3(name = "get_erasures")]
449            fn trait_get_erasures(&self) -> Vec<EdgeIndex> {
450                self.get_erasures()
451            }
452            #[pyo3(name = "get_syndrome")]
453            fn trait_get_syndrome(&self) -> SyndromePattern {
454                self.get_syndrome()
455            }
456            #[pyo3(name = "generate_random_errors", signature = (seed=thread_rng().gen()))]
457            fn trait_generate_random_errors(&mut self, seed: u64) -> SyndromePattern {
458                self.generate_random_errors(seed)
459            }
460            #[pyo3(name = "generate_errors")]
461            fn trait_generate_errors(&mut self, edge_indices: Vec<EdgeIndex>) -> SyndromePattern {
462                self.generate_errors(&edge_indices)
463            }
464            #[pyo3(name = "clear_errors")]
465            fn trait_clear_errors(&mut self) {
466                self.clear_errors()
467            }
468            #[pyo3(name = "is_virtual")]
469            fn trait_is_virtual(&mut self, vertex_idx: usize) -> bool {
470                self.is_virtual(vertex_idx)
471            }
472            #[pyo3(name = "is_defect")]
473            fn trait_is_defect(&mut self, vertex_idx: usize) -> bool {
474                self.is_defect(vertex_idx)
475            }
476            #[pyo3(name = "reorder_vertices")]
477            fn trait_reorder_vertices(&mut self, sequential_vertices: Vec<VertexIndex>) {
478                self.reorder_vertices(&sequential_vertices)
479            }
480            #[pyo3(name = "snapshot", signature = (abbrev=true))]
481            fn trait_snapshot(&mut self, abbrev: bool) -> PyObject {
482                json_to_pyobject(self.snapshot(abbrev))
483            }
484        }
485    };
486}
487
488impl<T> FusionVisualizer for T
489where
490    T: ExampleCode,
491{
492    fn snapshot(&self, abbrev: bool) -> serde_json::Value {
493        let (self_vertices, self_edges) = self.immutable_vertices_edges();
494        let mut vertices = Vec::<serde_json::Value>::new();
495        for vertex in self_vertices.iter() {
496            vertices.push(json!({
497                if abbrev { "v" } else { "is_virtual" }: i32::from(vertex.is_virtual),
498                if abbrev { "s" } else { "is_defect" }: i32::from(vertex.is_defect),
499            }));
500        }
501        let mut edges = Vec::<serde_json::Value>::new();
502        for edge in self_edges.iter() {
503            edges.push(json!({
504                if abbrev { "w" } else { "weight" }: edge.half_weight * 2,
505                if abbrev { "l" } else { "left" }: edge.vertices.0,
506                if abbrev { "r" } else { "right" }: edge.vertices.1,
507                // code itself is not capable of calculating growth
508            }));
509        }
510        json!({
511            "vertices": vertices,  // TODO: update HTML code to use the same language
512            "edges": edges,
513        })
514    }
515}
516
517/// perfect quantum repetition code
518#[derive(Clone, Debug)]
519#[cfg_attr(feature = "python_binding", cfg_eval)]
520#[cfg_attr(feature = "python_binding", pyclass)]
521pub struct CodeCapacityRepetitionCode {
522    /// vertices in the code
523    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
524    pub vertices: Vec<CodeVertex>,
525    /// nearest-neighbor edges in the decoding graph
526    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
527    pub edges: Vec<CodeEdge>,
528}
529
530impl ExampleCode for CodeCapacityRepetitionCode {
531    fn vertices_edges(&mut self) -> (&mut Vec<CodeVertex>, &mut Vec<CodeEdge>) {
532        (&mut self.vertices, &mut self.edges)
533    }
534    fn immutable_vertices_edges(&self) -> (&Vec<CodeVertex>, &Vec<CodeEdge>) {
535        (&self.vertices, &self.edges)
536    }
537}
538
539#[cfg(feature = "python_binding")]
540bind_trait_example_code! {CodeCapacityRepetitionCode}
541
542#[cfg_attr(feature = "python_binding", cfg_eval)]
543#[cfg_attr(feature = "python_binding", pymethods)]
544impl CodeCapacityRepetitionCode {
545    #[cfg_attr(feature = "python_binding", new)]
546    #[cfg_attr(feature = "python_binding", pyo3(signature = (d, p, max_half_weight = 500)))]
547    pub fn new(d: VertexNum, p: f64, max_half_weight: Weight) -> Self {
548        let mut code = Self::create_code(d);
549        code.set_probability(p);
550        code.compute_weights(max_half_weight);
551        code
552    }
553
554    #[cfg_attr(feature = "python_binding", staticmethod)]
555    #[allow(clippy::unnecessary_cast)]
556    pub fn create_code(d: VertexNum) -> Self {
557        assert!(d >= 3 && d % 2 == 1, "d must be odd integer >= 3");
558        let vertex_num = (d - 1) + 2; // two virtual vertices at left and right
559                                      // create edges
560        let mut edges = Vec::new();
561        for i in 0..d - 1 {
562            edges.push(CodeEdge::new(i, i + 1));
563        }
564        edges.push(CodeEdge::new(0, d)); // tje left-most edge
565        let mut code = Self {
566            vertices: Vec::new(),
567            edges,
568        };
569        // create vertices
570        code.fill_vertices(vertex_num);
571        code.vertices[d as usize - 1].is_virtual = true;
572        code.vertices[d as usize].is_virtual = true;
573        let mut positions = Vec::new();
574        for i in 0..d {
575            positions.push(VisualizePosition::new(0., i as f64, 0.));
576        }
577        positions.push(VisualizePosition::new(0., -1., 0.));
578        for (i, position) in positions.into_iter().enumerate() {
579            code.vertices[i].position = position;
580        }
581        code
582    }
583}
584
585/// code capacity noise model is a single measurement round with perfect stabilizer measurements;
586/// e.g. this is the decoding graph of a CSS surface code (standard one, not rotated one) with X-type stabilizers
587#[derive(Clone, Debug)]
588#[cfg_attr(feature = "python_binding", cfg_eval)]
589#[cfg_attr(feature = "python_binding", pyclass)]
590pub struct CodeCapacityPlanarCode {
591    /// vertices in the code
592    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
593    pub vertices: Vec<CodeVertex>,
594    /// nearest-neighbor edges in the decoding graph
595    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
596    pub edges: Vec<CodeEdge>,
597}
598
599impl ExampleCode for CodeCapacityPlanarCode {
600    fn vertices_edges(&mut self) -> (&mut Vec<CodeVertex>, &mut Vec<CodeEdge>) {
601        (&mut self.vertices, &mut self.edges)
602    }
603    fn immutable_vertices_edges(&self) -> (&Vec<CodeVertex>, &Vec<CodeEdge>) {
604        (&self.vertices, &self.edges)
605    }
606}
607
608#[cfg(feature = "python_binding")]
609bind_trait_example_code! {CodeCapacityPlanarCode}
610
611#[cfg_attr(feature = "python_binding", cfg_eval)]
612#[cfg_attr(feature = "python_binding", pymethods)]
613impl CodeCapacityPlanarCode {
614    #[cfg_attr(feature = "python_binding", new)]
615    #[cfg_attr(feature = "python_binding", pyo3(signature = (d, p, max_half_weight = 500)))]
616    pub fn new(d: VertexNum, p: f64, max_half_weight: Weight) -> Self {
617        let mut code = Self::create_code(d);
618        code.set_probability(p);
619        code.compute_weights(max_half_weight);
620        code
621    }
622
623    #[cfg_attr(feature = "python_binding", staticmethod)]
624    #[allow(clippy::unnecessary_cast)]
625    pub fn create_code(d: VertexNum) -> Self {
626        assert!(d >= 3 && d % 2 == 1, "d must be odd integer >= 3");
627        let row_vertex_num = (d - 1) + 2; // two virtual nodes at left and right
628        let vertex_num = row_vertex_num * d; // `d` rows
629                                             // create edges
630        let mut edges = Vec::new();
631        for row in 0..d {
632            let bias = row * row_vertex_num;
633            for i in 0..d - 1 {
634                edges.push(CodeEdge::new(bias + i, bias + i + 1));
635            }
636            edges.push(CodeEdge::new(bias, bias + d)); // left most edge
637            if row + 1 < d {
638                for i in 0..d - 1 {
639                    edges.push(CodeEdge::new(bias + i, bias + i + row_vertex_num));
640                }
641            }
642        }
643        let mut code = Self {
644            vertices: Vec::new(),
645            edges,
646        };
647        // create vertices
648        code.fill_vertices(vertex_num);
649        for row in 0..d {
650            let bias = row * row_vertex_num;
651            code.vertices[(bias + d - 1) as usize].is_virtual = true;
652            code.vertices[(bias + d) as usize].is_virtual = true;
653        }
654        let mut positions = Vec::new();
655        for row in 0..d {
656            let pos_i = row as f64;
657            for i in 0..d {
658                positions.push(VisualizePosition::new(pos_i, i as f64, 0.));
659            }
660            positions.push(VisualizePosition::new(pos_i, -1., 0.));
661        }
662        for (i, position) in positions.into_iter().enumerate() {
663            code.vertices[i].position = position;
664        }
665        code
666    }
667}
668
669/// phenomenological noise model is multiple measurement rounds adding only measurement errors
670/// e.g. this is the decoding graph of a CSS surface code (standard one, not rotated one) with X-type stabilizers
671#[derive(Clone, Debug)]
672#[cfg_attr(feature = "python_binding", cfg_eval)]
673#[cfg_attr(feature = "python_binding", pyclass)]
674pub struct PhenomenologicalPlanarCode {
675    /// vertices in the code
676    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
677    pub vertices: Vec<CodeVertex>,
678    /// nearest-neighbor edges in the decoding graph
679    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
680    pub edges: Vec<CodeEdge>,
681}
682
683impl ExampleCode for PhenomenologicalPlanarCode {
684    fn vertices_edges(&mut self) -> (&mut Vec<CodeVertex>, &mut Vec<CodeEdge>) {
685        (&mut self.vertices, &mut self.edges)
686    }
687    fn immutable_vertices_edges(&self) -> (&Vec<CodeVertex>, &Vec<CodeEdge>) {
688        (&self.vertices, &self.edges)
689    }
690}
691
692#[cfg(feature = "python_binding")]
693bind_trait_example_code! {PhenomenologicalPlanarCode}
694
695#[cfg_attr(feature = "python_binding", cfg_eval)]
696#[cfg_attr(feature = "python_binding", pymethods)]
697impl PhenomenologicalPlanarCode {
698    #[cfg_attr(feature = "python_binding", new)]
699    #[cfg_attr(feature = "python_binding", pyo3(signature = (d, noisy_measurements, p, max_half_weight = 500)))]
700    pub fn new(d: VertexNum, noisy_measurements: VertexNum, p: f64, max_half_weight: Weight) -> Self {
701        let mut code = Self::create_code(d, noisy_measurements);
702        code.set_probability(p);
703        code.compute_weights(max_half_weight);
704        code
705    }
706
707    #[cfg_attr(feature = "python_binding", staticmethod)]
708    #[allow(clippy::unnecessary_cast)]
709    pub fn create_code(d: VertexNum, noisy_measurements: VertexNum) -> Self {
710        assert!(d >= 3 && d % 2 == 1, "d must be odd integer >= 3");
711        let row_vertex_num = (d - 1) + 2; // two virtual nodes at left and right
712        let t_vertex_num = row_vertex_num * d; // `d` rows
713        let td = noisy_measurements + 1; // a perfect measurement round is capped at the end
714        let vertex_num = t_vertex_num * td; // `td` layers
715                                            // create edges
716        let mut edges = Vec::new();
717        for t in 0..td {
718            let t_bias = t * t_vertex_num;
719            for row in 0..d {
720                let bias = t_bias + row * row_vertex_num;
721                for i in 0..d - 1 {
722                    edges.push(CodeEdge::new(bias + i, bias + i + 1));
723                }
724                edges.push(CodeEdge::new(bias, bias + d)); // left most edge
725                if row + 1 < d {
726                    for i in 0..d - 1 {
727                        edges.push(CodeEdge::new(bias + i, bias + i + row_vertex_num));
728                    }
729                }
730            }
731            // inter-layer connection
732            if t + 1 < td {
733                for row in 0..d {
734                    let bias = t_bias + row * row_vertex_num;
735                    for i in 0..d - 1 {
736                        edges.push(CodeEdge::new(bias + i, bias + i + t_vertex_num));
737                    }
738                }
739            }
740        }
741        let mut code = Self {
742            vertices: Vec::new(),
743            edges,
744        };
745        // create vertices
746        code.fill_vertices(vertex_num);
747        for t in 0..td {
748            let t_bias = t * t_vertex_num;
749            for row in 0..d {
750                let bias = t_bias + row * row_vertex_num;
751                code.vertices[(bias + d - 1) as usize].is_virtual = true;
752                code.vertices[(bias + d) as usize].is_virtual = true;
753            }
754        }
755        let mut positions = Vec::new();
756        for t in 0..td {
757            let pos_t = t as f64;
758            for row in 0..d {
759                let pos_i = row as f64;
760                for i in 0..d {
761                    positions.push(VisualizePosition::new(pos_i, i as f64 + 0.5, pos_t));
762                }
763                positions.push(VisualizePosition::new(pos_i, -1. + 0.5, pos_t));
764            }
765        }
766        for (i, position) in positions.into_iter().enumerate() {
767            code.vertices[i].position = position;
768        }
769        code
770    }
771}
772
773/// (not accurate) circuit-level noise model is multiple measurement rounds with errors between each two-qubit gates
774/// e.g. this is the decoding graph of a CSS surface code (standard one, not rotated one) with X-type stabilizers
775#[derive(Clone, Debug)]
776#[cfg_attr(feature = "python_binding", cfg_eval)]
777#[cfg_attr(feature = "python_binding", pyclass)]
778pub struct CircuitLevelPlanarCode {
779    /// vertices in the code
780    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
781    pub vertices: Vec<CodeVertex>,
782    /// nearest-neighbor edges in the decoding graph
783    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
784    pub edges: Vec<CodeEdge>,
785}
786
787impl ExampleCode for CircuitLevelPlanarCode {
788    fn vertices_edges(&mut self) -> (&mut Vec<CodeVertex>, &mut Vec<CodeEdge>) {
789        (&mut self.vertices, &mut self.edges)
790    }
791    fn immutable_vertices_edges(&self) -> (&Vec<CodeVertex>, &Vec<CodeEdge>) {
792        (&self.vertices, &self.edges)
793    }
794}
795
796#[cfg(feature = "python_binding")]
797bind_trait_example_code! {CircuitLevelPlanarCode}
798
799#[cfg_attr(feature = "python_binding", cfg_eval)]
800#[cfg_attr(feature = "python_binding", pymethods)]
801impl CircuitLevelPlanarCode {
802    /// by default diagonal edge has error rate p/3 to mimic the behavior of unequal weights
803    #[cfg_attr(feature = "python_binding", new)]
804    #[cfg_attr(feature = "python_binding", pyo3(signature = (d, noisy_measurements, p, max_half_weight = 500)))]
805    pub fn new(d: VertexNum, noisy_measurements: VertexNum, p: f64, max_half_weight: Weight) -> Self {
806        Self::new_diagonal(d, noisy_measurements, p, max_half_weight, Some(p / 3.))
807    }
808
809    #[cfg_attr(feature = "python_binding", staticmethod)]
810    #[cfg_attr(feature = "python_binding", pyo3(signature = (d, noisy_measurements, p, max_half_weight = 500, diagonal_p = None)))]
811    #[allow(clippy::unnecessary_cast)]
812    pub fn new_diagonal(
813        d: VertexNum,
814        noisy_measurements: VertexNum,
815        p: f64,
816        max_half_weight: Weight,
817        diagonal_p: Option<f64>,
818    ) -> Self {
819        let mut code = Self::create_code(d, noisy_measurements);
820        code.set_probability(p);
821        if let Some(diagonal_p) = diagonal_p {
822            let (vertices, edges) = code.vertices_edges();
823            for edge in edges.iter_mut() {
824                let (v1, v2) = edge.vertices;
825                let v1p = &vertices[v1 as usize].position;
826                let v2p = &vertices[v2 as usize].position;
827                let manhattan_distance = (v1p.i - v2p.i).abs() + (v1p.j - v2p.j).abs() + (v1p.t - v2p.t).abs();
828                if manhattan_distance > 1. {
829                    edge.p = diagonal_p;
830                }
831            }
832        }
833        code.compute_weights(max_half_weight);
834        code
835    }
836
837    #[cfg_attr(feature = "python_binding", staticmethod)]
838    #[allow(clippy::unnecessary_cast)]
839    pub fn create_code(d: VertexNum, noisy_measurements: VertexNum) -> Self {
840        assert!(d >= 3 && d % 2 == 1, "d must be odd integer >= 3");
841        let row_vertex_num = (d - 1) + 2; // two virtual nodes at left and right
842        let t_vertex_num = row_vertex_num * d; // `d` rows
843        let td = noisy_measurements + 1; // a perfect measurement round is capped at the end
844        let vertex_num = t_vertex_num * td; // `td` layers
845                                            // create edges
846        let mut edges = Vec::new();
847        for t in 0..td {
848            let t_bias = t * t_vertex_num;
849            for row in 0..d {
850                let bias = t_bias + row * row_vertex_num;
851                for i in 0..d - 1 {
852                    edges.push(CodeEdge::new(bias + i, bias + i + 1));
853                }
854                edges.push(CodeEdge::new(bias, bias + d)); // left most edge
855                if row + 1 < d {
856                    for i in 0..d - 1 {
857                        edges.push(CodeEdge::new(bias + i, bias + i + row_vertex_num));
858                    }
859                }
860            }
861            // inter-layer connection
862            if t + 1 < td {
863                for row in 0..d {
864                    let bias = t_bias + row * row_vertex_num;
865                    for i in 0..d - 1 {
866                        edges.push(CodeEdge::new(bias + i, bias + i + t_vertex_num));
867                        let diagonal_diffs: Vec<(isize, isize)> = vec![(0, 1), (1, 0), (1, 1)];
868                        for (di, dj) in diagonal_diffs {
869                            let new_row = row as isize + di; // row corresponds to `i`
870                            let new_i = i as isize + dj; // i corresponds to `j`
871                            if new_row >= 0 && new_i >= 0 && new_row < d as isize && new_i < (d - 1) as isize {
872                                let new_bias = t_bias + (new_row as VertexNum) * row_vertex_num + t_vertex_num;
873                                edges.push(CodeEdge::new(bias + i, new_bias + new_i as VertexNum));
874                            }
875                        }
876                    }
877                }
878            }
879        }
880        let mut code = Self {
881            vertices: Vec::new(),
882            edges,
883        };
884        // create vertices
885        code.fill_vertices(vertex_num);
886        for t in 0..td {
887            let t_bias = t * t_vertex_num;
888            for row in 0..d {
889                let bias = t_bias + row * row_vertex_num;
890                code.vertices[(bias + d - 1) as usize].is_virtual = true;
891                code.vertices[(bias + d) as usize].is_virtual = true;
892            }
893        }
894        let mut positions = Vec::new();
895        for t in 0..td {
896            let pos_t = t as f64;
897            for row in 0..d {
898                let pos_i = row as f64;
899                for i in 0..d {
900                    positions.push(VisualizePosition::new(pos_i, i as f64 + 0.5, pos_t));
901                }
902                positions.push(VisualizePosition::new(pos_i, -1. + 0.5, pos_t));
903            }
904        }
905        for (i, position) in positions.into_iter().enumerate() {
906            code.vertices[i].position = position;
907        }
908        code
909    }
910}
911
912/// CSS surface code (the rotated one) with X-type stabilizers
913#[derive(Clone, Debug)]
914#[cfg_attr(feature = "python_binding", cfg_eval)]
915#[cfg_attr(feature = "python_binding", pyclass)]
916pub struct CodeCapacityRotatedCode {
917    /// vertices in the code
918    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
919    pub vertices: Vec<CodeVertex>,
920    /// nearest-neighbor edges in the decoding graph
921    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
922    pub edges: Vec<CodeEdge>,
923}
924
925impl ExampleCode for CodeCapacityRotatedCode {
926    fn vertices_edges(&mut self) -> (&mut Vec<CodeVertex>, &mut Vec<CodeEdge>) {
927        (&mut self.vertices, &mut self.edges)
928    }
929    fn immutable_vertices_edges(&self) -> (&Vec<CodeVertex>, &Vec<CodeEdge>) {
930        (&self.vertices, &self.edges)
931    }
932}
933
934#[cfg(feature = "python_binding")]
935bind_trait_example_code! {CodeCapacityRotatedCode}
936
937#[cfg_attr(feature = "python_binding", cfg_eval)]
938#[cfg_attr(feature = "python_binding", pymethods)]
939impl CodeCapacityRotatedCode {
940    #[cfg_attr(feature = "python_binding", new)]
941    #[cfg_attr(feature = "python_binding", pyo3(signature = (d, p, max_half_weight = 500)))]
942    pub fn new(d: VertexNum, p: f64, max_half_weight: Weight) -> Self {
943        let mut code = Self::create_code(d);
944        code.set_probability(p);
945        code.compute_weights(max_half_weight);
946        code
947    }
948
949    #[cfg_attr(feature = "python_binding", staticmethod)]
950    #[allow(clippy::unnecessary_cast)]
951    pub fn create_code(d: VertexNum) -> Self {
952        assert!(d >= 3 && d % 2 == 1, "d must be odd integer >= 3");
953        let row_vertex_num = (d - 1) / 2 + 1; // a virtual node at either left or right
954        let vertex_num = row_vertex_num * (d + 1); // d+1 rows
955                                                   // create edges
956        let mut edges = Vec::new();
957        for row in 0..d {
958            let bias = row * row_vertex_num;
959            if row % 2 == 0 {
960                for i in 0..d {
961                    if i % 2 == 0 {
962                        edges.push(CodeEdge::new(bias + i / 2, bias + row_vertex_num + i / 2));
963                    } else {
964                        edges.push(CodeEdge::new(bias + (i - 1) / 2, bias + row_vertex_num + (i + 1) / 2));
965                    }
966                }
967            } else {
968                for i in 0..d {
969                    if i % 2 == 0 {
970                        edges.push(CodeEdge::new(bias + i / 2, bias + row_vertex_num + i / 2));
971                    } else {
972                        edges.push(CodeEdge::new(bias + (i + 1) / 2, bias + row_vertex_num + (i - 1) / 2));
973                    }
974                }
975            }
976        }
977        let mut code = Self {
978            vertices: Vec::new(),
979            edges,
980        };
981        // create vertices
982        code.fill_vertices(vertex_num);
983        for row in 0..d + 1 {
984            let bias = row * row_vertex_num;
985            if row % 2 == 0 {
986                code.vertices[(bias + row_vertex_num - 1) as usize].is_virtual = true;
987            } else {
988                code.vertices[(bias) as usize].is_virtual = true;
989            }
990        }
991        let mut positions = Vec::new();
992        for row in 0..d + 1 {
993            let pos_i = row as f64;
994            for i in 0..row_vertex_num {
995                let pos_bias = (row % 2 == 0) as VertexNum;
996                positions.push(VisualizePosition::new(pos_i, (i * 2 + pos_bias) as f64, 0.));
997            }
998        }
999        for (i, position) in positions.into_iter().enumerate() {
1000            code.vertices[i].position = position;
1001        }
1002        code
1003    }
1004}
1005
1006/// CSS surface code (the rotated one) with X-type stabilizers
1007#[derive(Clone, Debug)]
1008#[cfg_attr(feature = "python_binding", cfg_eval)]
1009#[cfg_attr(feature = "python_binding", pyclass)]
1010pub struct PhenomenologicalRotatedCode {
1011    /// vertices in the code
1012    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
1013    pub vertices: Vec<CodeVertex>,
1014    /// nearest-neighbor edges in the decoding graph
1015    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
1016    pub edges: Vec<CodeEdge>,
1017}
1018
1019impl ExampleCode for PhenomenologicalRotatedCode {
1020    fn vertices_edges(&mut self) -> (&mut Vec<CodeVertex>, &mut Vec<CodeEdge>) {
1021        (&mut self.vertices, &mut self.edges)
1022    }
1023    fn immutable_vertices_edges(&self) -> (&Vec<CodeVertex>, &Vec<CodeEdge>) {
1024        (&self.vertices, &self.edges)
1025    }
1026}
1027
1028#[cfg(feature = "python_binding")]
1029bind_trait_example_code! {PhenomenologicalRotatedCode}
1030
1031#[cfg_attr(feature = "python_binding", cfg_eval)]
1032#[cfg_attr(feature = "python_binding", pymethods)]
1033impl PhenomenologicalRotatedCode {
1034    #[cfg_attr(feature = "python_binding", new)]
1035    #[cfg_attr(feature = "python_binding", pyo3(signature = (d, noisy_measurements, p, max_half_weight = 500)))]
1036    pub fn new(d: VertexNum, noisy_measurements: VertexNum, p: f64, max_half_weight: Weight) -> Self {
1037        let mut code = Self::create_code(d, noisy_measurements);
1038        code.set_probability(p);
1039        code.compute_weights(max_half_weight);
1040        code
1041    }
1042
1043    #[cfg_attr(feature = "python_binding", staticmethod)]
1044    #[allow(clippy::unnecessary_cast)]
1045    pub fn create_code(d: VertexNum, noisy_measurements: VertexNum) -> Self {
1046        assert!(d >= 3 && d % 2 == 1, "d must be odd integer >= 3");
1047        let row_vertex_num = (d - 1) / 2 + 1; // a virtual node at either left or right
1048        let t_vertex_num = row_vertex_num * (d + 1); // d+1 rows
1049        let td = noisy_measurements + 1; // a perfect measurement round is capped at the end
1050        let vertex_num = t_vertex_num * td; // `td` layers
1051                                            // create edges
1052        let mut edges = Vec::new();
1053        for t in 0..td {
1054            let t_bias = t * t_vertex_num;
1055            for row in 0..d {
1056                let bias = t_bias + row * row_vertex_num;
1057                if row % 2 == 0 {
1058                    for i in 0..d {
1059                        if i % 2 == 0 {
1060                            edges.push(CodeEdge::new(bias + i / 2, bias + row_vertex_num + i / 2));
1061                        } else {
1062                            edges.push(CodeEdge::new(bias + (i - 1) / 2, bias + row_vertex_num + (i + 1) / 2));
1063                        }
1064                    }
1065                } else {
1066                    for i in 0..d {
1067                        if i % 2 == 0 {
1068                            edges.push(CodeEdge::new(bias + i / 2, bias + row_vertex_num + i / 2));
1069                        } else {
1070                            edges.push(CodeEdge::new(bias + (i + 1) / 2, bias + row_vertex_num + (i - 1) / 2));
1071                        }
1072                    }
1073                }
1074            }
1075            // inter-layer connection
1076            if t + 1 < td {
1077                for row in 0..d + 1 {
1078                    let bias = t_bias + row * row_vertex_num;
1079                    for i in 0..row_vertex_num {
1080                        edges.push(CodeEdge::new(bias + i, bias + i + t_vertex_num));
1081                    }
1082                }
1083            }
1084        }
1085        let mut code = Self {
1086            vertices: Vec::new(),
1087            edges,
1088        };
1089        // create vertices
1090        code.fill_vertices(vertex_num);
1091        for t in 0..td {
1092            let t_bias = t * t_vertex_num;
1093            for row in 0..d + 1 {
1094                let bias = t_bias + row * row_vertex_num;
1095                if row % 2 == 0 {
1096                    code.vertices[(bias + row_vertex_num - 1) as usize].is_virtual = true;
1097                } else {
1098                    code.vertices[(bias) as usize].is_virtual = true;
1099                }
1100            }
1101        }
1102        let mut positions = Vec::new();
1103        for t in 0..td {
1104            let pos_t = t as f64 * 2f64.sqrt();
1105            for row in 0..d + 1 {
1106                let pos_i = row as f64;
1107                for i in 0..row_vertex_num {
1108                    let pos_bias = (row % 2 == 0) as VertexNum;
1109                    positions.push(VisualizePosition::new(pos_i, (i * 2 + pos_bias) as f64, pos_t));
1110                }
1111            }
1112        }
1113        for (i, position) in positions.into_iter().enumerate() {
1114            code.vertices[i].position = position;
1115        }
1116        code
1117    }
1118}
1119
1120/// example code with QEC-Playground as simulator
1121#[cfg(feature = "qecp_integrate")]
1122#[cfg_attr(feature = "python_binding", cfg_eval)]
1123#[cfg_attr(feature = "python_binding", pyclass)]
1124pub struct QECPlaygroundCode {
1125    simulator: qecp::simulator::Simulator,
1126    noise_model: std::sync::Arc<qecp::noise_model::NoiseModel>,
1127    adaptor: std::sync::Arc<qecp::decoder_fusion::FusionBlossomAdaptor>,
1128    vertex_index_map: std::sync::Arc<HashMap<usize, VertexIndex>>,
1129    edge_index_map: std::sync::Arc<HashMap<usize, EdgeIndex>>,
1130    /// vertices in the code
1131    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
1132    pub vertices: Vec<CodeVertex>,
1133    /// nearest-neighbor edges in the decoding graph
1134    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
1135    pub edges: Vec<CodeEdge>,
1136}
1137
1138#[cfg(feature = "qecp_integrate")]
1139impl ExampleCode for QECPlaygroundCode {
1140    fn vertices_edges(&mut self) -> (&mut Vec<CodeVertex>, &mut Vec<CodeEdge>) {
1141        (&mut self.vertices, &mut self.edges)
1142    }
1143    fn immutable_vertices_edges(&self) -> (&Vec<CodeVertex>, &Vec<CodeEdge>) {
1144        (&self.vertices, &self.edges)
1145    }
1146    // override simulation function
1147    #[allow(clippy::unnecessary_cast)]
1148    fn generate_random_errors(&mut self, seed: u64) -> SyndromePattern {
1149        use qecp::simulator::SimulatorGenerics;
1150        let rng = qecp::reproducible_rand::Xoroshiro128StarStar::seed_from_u64(seed);
1151        self.simulator.set_rng(rng);
1152        let (error_count, erasure_count) = self.simulator.generate_random_errors(&self.noise_model);
1153        let sparse_detected_erasures = if erasure_count != 0 {
1154            self.simulator.generate_sparse_detected_erasures()
1155        } else {
1156            qecp::simulator::SparseErasures::new()
1157        };
1158        let sparse_measurement = if error_count != 0 {
1159            self.simulator.generate_sparse_measurement()
1160        } else {
1161            qecp::simulator::SparseMeasurement::new()
1162        };
1163        let syndrome_pattern = self
1164            .adaptor
1165            .generate_syndrome_pattern(&sparse_measurement, &sparse_detected_erasures);
1166        for vertex in self.vertices.iter_mut() {
1167            vertex.is_defect = false;
1168        }
1169        for &vertex_index in syndrome_pattern.defect_vertices.iter() {
1170            if let Some(new_index) = self.vertex_index_map.get(&vertex_index) {
1171                self.vertices[*new_index as usize].is_defect = true;
1172            }
1173        }
1174        for edge in self.edges.iter_mut() {
1175            edge.is_erasure = false;
1176        }
1177        for &edge_index in syndrome_pattern.erasures.iter() {
1178            if let Some(new_index) = self.edge_index_map.get(&edge_index) {
1179                self.edges[*new_index as usize].is_erasure = true;
1180            }
1181        }
1182        self.get_syndrome()
1183    }
1184}
1185
1186#[cfg(all(feature = "qecp_integrate", feature = "python_binding"))]
1187bind_trait_example_code! {QECPlaygroundCode}
1188
1189#[cfg(feature = "qecp_integrate")]
1190#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
1191#[serde(deny_unknown_fields)]
1192pub struct QECPlaygroundCodeConfig {
1193    // default to d
1194    pub di: Option<usize>,
1195    pub dj: Option<usize>,
1196    pub nm: Option<usize>,
1197    #[serde(default = "qec_playground_default_configs::pe")]
1198    pub pe: f64,
1199    pub noise_model_modifier: Option<serde_json::Value>,
1200    #[serde(default = "qec_playground_default_configs::code_type")]
1201    pub code_type: qecp::code_builder::CodeType,
1202    #[serde(default = "qec_playground_default_configs::bias_eta")]
1203    pub bias_eta: f64,
1204    pub noise_model: Option<qecp::noise_model_builder::NoiseModelBuilder>,
1205    #[serde(default = "qec_playground_default_configs::noise_model_configuration")]
1206    pub noise_model_configuration: serde_json::Value,
1207    #[serde(default = "qec_playground_default_configs::parallel_init")]
1208    pub parallel_init: usize,
1209    #[serde(default = "qec_playground_default_configs::use_brief_edge")]
1210    pub use_brief_edge: bool,
1211    // specify the target qubit type
1212    pub qubit_type: Option<qecp::types::QubitType>,
1213    #[serde(default = "qecp::decoder_fusion::fusion_default_configs::max_half_weight")]
1214    pub max_half_weight: usize,
1215    #[serde(default = "qec_playground_default_configs::trim_isolated_vertices")]
1216    pub trim_isolated_vertices: bool,
1217}
1218
1219#[cfg(feature = "qecp_integrate")]
1220pub mod qec_playground_default_configs {
1221    pub fn pe() -> f64 {
1222        0.
1223    }
1224    pub fn bias_eta() -> f64 {
1225        0.5
1226    }
1227    pub fn noise_model_configuration() -> serde_json::Value {
1228        json!({})
1229    }
1230    pub fn code_type() -> qecp::code_builder::CodeType {
1231        qecp::code_builder::CodeType::StandardPlanarCode
1232    }
1233    pub fn parallel_init() -> usize {
1234        1
1235    }
1236    pub fn use_brief_edge() -> bool {
1237        false
1238    }
1239    pub fn trim_isolated_vertices() -> bool {
1240        true
1241    }
1242}
1243
1244#[cfg(feature = "qecp_integrate")]
1245impl QECPlaygroundCode {
1246    #[allow(clippy::unnecessary_cast)]
1247    pub fn new(d: usize, p: f64, config: serde_json::Value) -> Self {
1248        let config: QECPlaygroundCodeConfig = serde_json::from_value(config).unwrap();
1249        let di = config.di.unwrap_or(d);
1250        let dj = config.dj.unwrap_or(d);
1251        let nm = config.nm.unwrap_or(d);
1252        let mut simulator = qecp::simulator::Simulator::new(config.code_type, qecp::code_builder::CodeSize::new(nm, di, dj));
1253        let mut noise_model = qecp::noise_model::NoiseModel::new(&simulator);
1254        let px = p / (1. + config.bias_eta) / 2.;
1255        let py = px;
1256        let pz = p - 2. * px;
1257        simulator.set_error_rates(&mut noise_model, px, py, pz, config.pe);
1258        // apply customized noise model
1259        if let Some(noise_model_builder) = &config.noise_model {
1260            noise_model_builder.apply(
1261                &mut simulator,
1262                &mut noise_model,
1263                &config.noise_model_configuration,
1264                p,
1265                config.bias_eta,
1266                config.pe,
1267            );
1268        }
1269        simulator.compress_error_rates(&mut noise_model); // by default compress all error rates
1270        let noise_model = std::sync::Arc::new(noise_model);
1271        // construct vertices and edges
1272        let fusion_decoder = qecp::decoder_fusion::FusionDecoder::new(
1273            &simulator,
1274            noise_model.clone(),
1275            &serde_json::from_value(json!({
1276                "max_half_weight": config.max_half_weight
1277            }))
1278            .unwrap(),
1279            config.parallel_init,
1280            config.use_brief_edge,
1281        );
1282        let adaptor = fusion_decoder.adaptor;
1283        let initializer = &adaptor.initializer;
1284        let positions = &adaptor.positions;
1285        let mut vertex_index_map = HashMap::new();
1286        // filter the specific qubit type and also remove isolated virtual vertices
1287        let is_vertex_isolated = if config.trim_isolated_vertices {
1288            let mut is_vertex_isolated = vec![true; initializer.vertex_num];
1289            for (left_vertex, right_vertex, _) in initializer.weighted_edges.iter().cloned() {
1290                is_vertex_isolated[left_vertex] = false;
1291                is_vertex_isolated[right_vertex] = false;
1292            }
1293            is_vertex_isolated
1294        } else {
1295            vec![false; initializer.vertex_num]
1296        };
1297        for (vertex_index, is_isolated) in is_vertex_isolated.iter().cloned().enumerate() {
1298            let position = &adaptor.vertex_to_position_mapping[vertex_index];
1299            let qubit_type = simulator.get_node(position).as_ref().unwrap().qubit_type;
1300            if !config.qubit_type.is_some_and(|expect| expect != qubit_type) && !is_isolated {
1301                let new_index = vertex_index_map.len() as VertexIndex;
1302                vertex_index_map.insert(vertex_index, new_index);
1303            }
1304        }
1305        let mut code = Self {
1306            simulator,
1307            noise_model,
1308            adaptor: adaptor.clone(),
1309            vertex_index_map: std::sync::Arc::new(vertex_index_map),
1310            edge_index_map: std::sync::Arc::new(HashMap::new()), // overwrite later
1311            vertices: Vec::with_capacity(initializer.vertex_num),
1312            edges: Vec::with_capacity(initializer.weighted_edges.len()),
1313        };
1314        let mut edge_index_map = HashMap::new();
1315        for (edge_index, (left_vertex, right_vertex, weight)) in initializer.weighted_edges.iter().cloned().enumerate() {
1316            assert!(weight % 2 == 0, "weight must be even number");
1317            let contains_left = code.vertex_index_map.contains_key(&left_vertex);
1318            let contains_right = code.vertex_index_map.contains_key(&right_vertex);
1319            assert_eq!(contains_left, contains_right, "should not connect different type of qubits");
1320            if contains_left {
1321                let new_index = edge_index_map.len() as EdgeIndex;
1322                edge_index_map.insert(edge_index, new_index);
1323                code.edges.push(CodeEdge {
1324                    vertices: (code.vertex_index_map[&left_vertex], code.vertex_index_map[&right_vertex]),
1325                    p: 0.,  // doesn't matter
1326                    pe: 0., // doesn't matter
1327                    half_weight: (weight as Weight) / 2,
1328                    is_erasure: false, // doesn't matter
1329                });
1330            }
1331        }
1332        code.edge_index_map = std::sync::Arc::new(edge_index_map);
1333        // automatically create the vertices and nearest-neighbor connection
1334        code.fill_vertices(code.vertex_index_map.len() as VertexNum);
1335        // set virtual vertices and positions
1336        for (vertex_index, position) in positions.iter().cloned().enumerate() {
1337            if let Some(new_index) = code.vertex_index_map.get(&vertex_index) {
1338                code.vertices[*new_index as usize].position = VisualizePosition::new(position.i, position.j, position.t);
1339            }
1340        }
1341        for vertex_index in initializer.virtual_vertices.iter() {
1342            if let Some(new_index) = code.vertex_index_map.get(vertex_index) {
1343                code.vertices[*new_index as usize].is_virtual = true;
1344            }
1345        }
1346        code
1347    }
1348}
1349
1350/// read from file, including the error patterns;
1351/// the point is to avoid bad cache performance, because generating random error requires iterating over a large memory space,
1352/// invalidating all cache. also, this can reduce the time of decoding by prepare the data before hand and could be shared between
1353/// different partition configurations
1354#[derive(Clone, Debug)]
1355#[cfg_attr(feature = "python_binding", cfg_eval)]
1356#[cfg_attr(feature = "python_binding", pyclass)]
1357pub struct ErrorPatternReader {
1358    /// vertices in the code
1359    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
1360    pub vertices: Vec<CodeVertex>,
1361    /// nearest-neighbor edges in the decoding graph
1362    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
1363    pub edges: Vec<CodeEdge>,
1364    /// pre-generated syndrome patterns
1365    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
1366    pub syndrome_patterns: Vec<SyndromePattern>,
1367    /// cursor of current errors
1368    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
1369    pub defect_index: usize,
1370    #[cfg_attr(feature = "python_binding", pyo3(get, set))]
1371    pub cyclic_syndrome: bool,
1372}
1373
1374impl ExampleCode for ErrorPatternReader {
1375    fn vertices_edges(&mut self) -> (&mut Vec<CodeVertex>, &mut Vec<CodeEdge>) {
1376        (&mut self.vertices, &mut self.edges)
1377    }
1378    fn immutable_vertices_edges(&self) -> (&Vec<CodeVertex>, &Vec<CodeEdge>) {
1379        (&self.vertices, &self.edges)
1380    }
1381    fn generate_random_errors(&mut self, _seed: u64) -> SyndromePattern {
1382        if self.cyclic_syndrome {
1383            if self.defect_index >= self.syndrome_patterns.len() {
1384                self.defect_index = 0; // cyclic
1385            }
1386        } else {
1387            assert!(
1388                self.defect_index < self.syndrome_patterns.len(),
1389                "reading syndrome pattern more than in the file, consider generate the file with more data points"
1390            );
1391        }
1392        let syndrome_pattern = self.syndrome_patterns[self.defect_index].clone();
1393        self.defect_index += 1;
1394        syndrome_pattern
1395    }
1396}
1397
1398#[cfg_attr(feature = "python_binding", cfg_eval)]
1399#[cfg_attr(feature = "python_binding", pymethods)]
1400impl ErrorPatternReader {
1401    #[allow(clippy::unnecessary_cast)]
1402    #[cfg_attr(feature = "python_binding", new)]
1403    #[cfg_attr(feature = "python_binding", pyo3(signature = (filename, cyclic_syndrome = false)))]
1404    pub fn py_new(filename: String, cyclic_syndrome: bool) -> Self {
1405        Self::new(json!({
1406            "filename": filename,
1407            "cyclic_syndrome": cyclic_syndrome,
1408        }))
1409    }
1410}
1411
1412#[cfg(feature = "python_binding")]
1413bind_trait_example_code! {ErrorPatternReader}
1414
1415impl ErrorPatternReader {
1416    #[allow(clippy::unnecessary_cast)]
1417    pub fn new(mut config: serde_json::Value) -> Self {
1418        let mut filename = "tmp/syndrome_patterns.txt".to_string();
1419        let config = config.as_object_mut().expect("config must be JSON object");
1420        if let Some(value) = config.remove("filename") {
1421            filename = value.as_str().expect("filename string").to_string();
1422        }
1423        let cyclic_syndrome = if let Some(cyclic_syndrome) = config.remove("cyclic_syndrome") {
1424            cyclic_syndrome.as_bool().expect("cyclic_syndrome: bool")
1425        } else {
1426            false
1427        }; // by default not enable cyclic syndrome, to avoid problem
1428        if !config.is_empty() {
1429            panic!("unknown config keys: {:?}", config.keys().collect::<Vec<&String>>());
1430        }
1431        let file = File::open(filename).unwrap();
1432        let mut syndrome_patterns = vec![];
1433        let mut initializer: Option<SolverInitializer> = None;
1434        let mut positions: Option<Vec<VisualizePosition>> = None;
1435        for (line_index, line) in io::BufReader::new(file).lines().enumerate() {
1436            if let Ok(value) = line {
1437                match line_index {
1438                    0 => {
1439                        assert!(value.starts_with("Syndrome Pattern v1.0 "), "incompatible file version");
1440                    }
1441                    1 => {
1442                        initializer = Some(serde_json::from_str(&value).unwrap());
1443                    }
1444                    2 => {
1445                        positions = Some(serde_json::from_str(&value).unwrap());
1446                    }
1447                    _ => {
1448                        let syndrome_pattern: SyndromePattern = serde_json::from_str(&value).unwrap();
1449                        syndrome_patterns.push(syndrome_pattern);
1450                    }
1451                }
1452            }
1453        }
1454        let initializer = initializer.expect("initializer not present in file");
1455        let positions = positions.expect("positions not present in file");
1456        assert_eq!(positions.len(), initializer.vertex_num as usize);
1457        let mut code = Self {
1458            vertices: Vec::with_capacity(initializer.vertex_num as usize),
1459            edges: Vec::with_capacity(initializer.weighted_edges.len()),
1460            syndrome_patterns,
1461            defect_index: 0,
1462            cyclic_syndrome,
1463        };
1464        for (left_vertex, right_vertex, weight) in initializer.weighted_edges.iter() {
1465            assert!(weight % 2 == 0, "weight must be even number");
1466            code.edges.push(CodeEdge {
1467                vertices: (*left_vertex, *right_vertex),
1468                p: 0.,  // doesn't matter
1469                pe: 0., // doesn't matter
1470                half_weight: weight / 2,
1471                is_erasure: false, // doesn't matter
1472            });
1473        }
1474        // automatically create the vertices and nearest-neighbor connection
1475        code.fill_vertices(initializer.vertex_num);
1476        // set virtual vertices and positions
1477        for (vertex_index, position) in positions.into_iter().enumerate() {
1478            code.vertices[vertex_index].position = position;
1479        }
1480        for vertex_index in initializer.virtual_vertices {
1481            code.vertices[vertex_index as usize].is_virtual = true;
1482        }
1483        code
1484    }
1485}
1486
1487/// generate error patterns in parallel by hold multiple instances of the same code type
1488pub struct ExampleCodeParallel<CodeType: ExampleCode + Sync + Send + Clone> {
1489    /// used to provide graph
1490    pub example: CodeType,
1491    /// list of codes
1492    pub codes: Vec<ArcRwLock<CodeType>>,
1493    /// syndrome patterns generated by individual code
1494    pub syndrome_patterns: Vec<SyndromePattern>,
1495    /// currently using code
1496    pub code_index: usize,
1497}
1498
1499impl<CodeType: ExampleCode + Sync + Send + Clone> ExampleCodeParallel<CodeType> {
1500    pub fn new(example: CodeType, code_count: usize) -> Self {
1501        let mut codes = vec![];
1502        for _ in 0..code_count {
1503            codes.push(ArcRwLock::<CodeType>::new_value(example.clone()));
1504        }
1505        Self {
1506            example,
1507            codes,
1508            syndrome_patterns: vec![],
1509            code_index: 0,
1510        }
1511    }
1512}
1513
1514impl<CodeType: ExampleCode + Sync + Send + Clone> ExampleCode for ExampleCodeParallel<CodeType> {
1515    fn vertices_edges(&mut self) -> (&mut Vec<CodeVertex>, &mut Vec<CodeEdge>) {
1516        self.example.vertices_edges()
1517    }
1518    fn immutable_vertices_edges(&self) -> (&Vec<CodeVertex>, &Vec<CodeEdge>) {
1519        self.example.immutable_vertices_edges()
1520    }
1521    fn generate_random_errors(&mut self, seed: u64) -> SyndromePattern {
1522        if self.code_index == 0 {
1523            // run generator in parallel
1524            (0..self.codes.len())
1525                .into_par_iter()
1526                .map(|code_index| {
1527                    self.codes[code_index]
1528                        .write()
1529                        .generate_random_errors(seed + (code_index * 1_000_000_000) as u64)
1530                })
1531                .collect_into_vec(&mut self.syndrome_patterns);
1532        }
1533        let syndrome_pattern = self.syndrome_patterns[self.code_index].clone();
1534        self.code_index = (self.code_index + 1) % self.codes.len();
1535        syndrome_pattern
1536    }
1537}
1538
1539#[cfg(feature = "python_binding")]
1540#[pyfunction]
1541pub(crate) fn register(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
1542    m.add_class::<CodeVertex>()?;
1543    m.add_class::<CodeEdge>()?;
1544    m.add_function(wrap_pyfunction!(weight_of_p, m)?)?;
1545    m.add_class::<CodeCapacityRepetitionCode>()?;
1546    m.add_class::<CodeCapacityPlanarCode>()?;
1547    m.add_class::<PhenomenologicalPlanarCode>()?;
1548    m.add_class::<CircuitLevelPlanarCode>()?;
1549    m.add_class::<CodeCapacityRotatedCode>()?;
1550    m.add_class::<PhenomenologicalRotatedCode>()?;
1551    m.add_class::<ErrorPatternReader>()?;
1552    Ok(())
1553}
1554
1555pub fn visualize_code(code: &mut impl ExampleCode, visualize_filename: String) {
1556    print_visualize_link(visualize_filename.clone());
1557    let mut visualizer = Visualizer::new(
1558        Some(visualize_data_folder() + visualize_filename.as_str()),
1559        code.get_positions(),
1560        true,
1561    )
1562    .unwrap();
1563    visualizer.snapshot("code".to_string(), code).unwrap();
1564    for round in 0..3 {
1565        code.generate_random_errors(round);
1566        visualizer.snapshot(format!("syndrome {}", round + 1), code).unwrap();
1567    }
1568}
1569
1570#[cfg(test)]
1571mod tests {
1572    use super::*;
1573
1574    #[test]
1575    fn example_code_capacity_repetition_code() {
1576        // cargo test example_code_capacity_repetition_code -- --nocapture
1577        let mut code = CodeCapacityRepetitionCode::new(7, 0.2, 500);
1578        code.sanity_check().unwrap();
1579        visualize_code(&mut code, "example_code_capacity_repetition_code.json".to_string());
1580    }
1581
1582    #[test]
1583    fn example_code_capacity_planar_code() {
1584        // cargo test example_code_capacity_planar_code -- --nocapture
1585        let mut code = CodeCapacityPlanarCode::new(7, 0.1, 500);
1586        code.sanity_check().unwrap();
1587        visualize_code(&mut code, "example_code_capacity_planar_code.json".to_string());
1588    }
1589
1590    #[test]
1591    fn example_phenomenological_planar_code() {
1592        // cargo test example_phenomenological_planar_code -- --nocapture
1593        let mut code = PhenomenologicalPlanarCode::new(7, 7, 0.01, 500);
1594        code.sanity_check().unwrap();
1595        visualize_code(&mut code, "example_phenomenological_planar_code.json".to_string());
1596    }
1597
1598    #[test]
1599    fn example_large_phenomenological_planar_code() {
1600        // cargo test example_large_phenomenological_planar_code -- --nocapture
1601        let mut code = PhenomenologicalPlanarCode::new(7, 30, 0.01, 500);
1602        code.sanity_check().unwrap();
1603        visualize_code(&mut code, "example_large_phenomenological_planar_code.json".to_string());
1604    }
1605
1606    #[test]
1607    fn example_circuit_level_planar_code() {
1608        // cargo test example_circuit_level_planar_code -- --nocapture
1609        let mut code = CircuitLevelPlanarCode::new(7, 7, 0.01, 500);
1610        code.sanity_check().unwrap();
1611        visualize_code(&mut code, "example_circuit_level_planar_code.json".to_string());
1612    }
1613
1614    #[test]
1615    fn example_code_capacity_rotated_code() {
1616        // cargo test example_code_capacity_rotated_code -- --nocapture
1617        let mut code = CodeCapacityRotatedCode::new(5, 0.1, 500);
1618        code.sanity_check().unwrap();
1619        visualize_code(&mut code, "example_code_capacity_rotated_code.json".to_string());
1620    }
1621
1622    #[test]
1623    fn example_code_phenomenological_rotated_code() {
1624        // cargo test example_code_phenomenological_rotated_code -- --nocapture
1625        let mut code = PhenomenologicalRotatedCode::new(5, 5, 0.01, 500);
1626        code.sanity_check().unwrap();
1627        visualize_code(&mut code, "example_code_phenomenological_rotated_code.json".to_string());
1628    }
1629
1630    #[cfg(feature = "qecp_integrate")]
1631    #[test]
1632    fn example_qec_playground_code() {
1633        // cargo test example_qec_playground_code -- --nocapture
1634        let config = json!({
1635            "qubit_type": qecp::types::QubitType::StabZ,
1636            "max_half_weight": 50,
1637        });
1638        let mut code = QECPlaygroundCode::new(5, 0.001, config);
1639        code.sanity_check().unwrap();
1640        visualize_code(&mut code, "example_qec_playground_code.json".to_string());
1641    }
1642}