fusion_blossom/
lib.rs

1#![cfg_attr(feature = "unsafe_pointer", feature(get_mut_unchecked))]
2#![cfg_attr(feature = "unsafe_pointer", allow(unused_mut))]
3#![cfg_attr(feature = "python_binding", feature(cfg_eval))]
4
5extern crate cfg_if;
6extern crate libc;
7extern crate parking_lot;
8extern crate priority_queue;
9extern crate rand_xoshiro;
10extern crate serde;
11#[macro_use]
12extern crate serde_json;
13extern crate chrono;
14extern crate clap;
15extern crate core_affinity;
16extern crate derivative;
17extern crate pbr;
18#[cfg(test)]
19extern crate petgraph;
20#[cfg(feature = "python_binding")]
21extern crate pyo3;
22#[cfg(feature = "qecp_integrate")]
23pub extern crate qecp;
24extern crate rand;
25extern crate rayon;
26extern crate urlencoding;
27extern crate weak_table;
28
29pub mod blossom_v;
30pub mod cli;
31pub mod complete_graph;
32pub mod dual_module;
33pub mod dual_module_parallel;
34pub mod dual_module_serial;
35pub mod example_codes;
36pub mod example_partition;
37pub mod mwpm_solver;
38pub mod pointers;
39pub mod primal_module;
40pub mod primal_module_parallel;
41pub mod primal_module_serial;
42pub mod util;
43pub mod visualize;
44#[cfg(feature = "python_binding")]
45use pyo3::prelude::*;
46
47use complete_graph::*;
48use util::*;
49
50#[cfg(feature = "python_binding")]
51#[pymodule]
52fn fusion_blossom(py: Python<'_>, m: &PyModule) -> PyResult<()> {
53    util::register(py, m)?;
54    mwpm_solver::register(py, m)?;
55    example_codes::register(py, m)?;
56    visualize::register(py, m)?;
57    primal_module::register(py, m)?;
58    let helper_code = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/helper.py"));
59    let helper_module = PyModule::from_code(py, helper_code, "helper", "helper")?;
60    helper_module.add("visualizer_website", generate_visualizer_website(py))?;
61    let bottle_code = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/bottle.py")); // embed bottle
62    helper_module.add_submodule(PyModule::from_code(py, bottle_code, "bottle", "bottle")?)?;
63    m.add_submodule(helper_module)?;
64    let helper_register = helper_module.getattr("register")?;
65    helper_register.call1((m,))?;
66    Ok(())
67}
68
69/// use fusion blossom to solve MWPM (to optimize speed, consider reuse a [`mwpm_solver::SolverSerial`] object)
70#[allow(clippy::unnecessary_cast)]
71pub fn fusion_mwpm(initializer: &SolverInitializer, syndrome_pattern: &SyndromePattern) -> Vec<VertexIndex> {
72    // sanity check
73    assert!(initializer.vertex_num > 1, "at least one vertex required");
74    let max_safe_weight = ((Weight::MAX as usize) / initializer.vertex_num as usize) as Weight;
75    for (i, j, weight) in initializer.weighted_edges.iter() {
76        if weight > &max_safe_weight {
77            panic!(
78                "edge {}-{} has weight {} > max safe weight {}, it may cause fusion blossom to overflow",
79                i, j, weight, max_safe_weight
80            );
81        }
82    }
83    // by default use serial implementation fusion blossom
84    mwpm_solver::LegacySolverSerial::mwpm_solve(initializer, syndrome_pattern)
85}
86
87/// fall back to use blossom V library to solve MWPM (install blossom V required)
88#[allow(clippy::unnecessary_cast)]
89pub fn blossom_v_mwpm(initializer: &SolverInitializer, defect_vertices: &[VertexIndex]) -> Vec<VertexIndex> {
90    // this feature will be automatically enabled if you install blossom V source code, see README.md for more information
91    if cfg!(not(feature = "blossom_v")) {
92        panic!("need blossom V library, see README.md")
93    }
94    // sanity check
95    assert!(initializer.vertex_num > 1, "at least one vertex required");
96    let max_safe_weight = ((i32::MAX as usize) / initializer.vertex_num as usize) as Weight;
97    for (i, j, weight) in initializer.weighted_edges.iter() {
98        if weight > &max_safe_weight {
99            panic!(
100                "edge {}-{} has weight {} > max safe weight {}, it may cause blossom V library to overflow",
101                i, j, weight, max_safe_weight
102            );
103        }
104    }
105    let mut complete_graph = CompleteGraph::new(initializer.vertex_num, &initializer.weighted_edges);
106    blossom_v_mwpm_reuse(&mut complete_graph, initializer, defect_vertices)
107}
108
109#[allow(clippy::unnecessary_cast)]
110pub fn blossom_v_mwpm_reuse(
111    complete_graph: &mut CompleteGraph,
112    initializer: &SolverInitializer,
113    defect_vertices: &[VertexIndex],
114) -> Vec<VertexIndex> {
115    // first collect virtual vertices and real vertices
116    let mut is_virtual: Vec<bool> = (0..initializer.vertex_num).map(|_| false).collect();
117    let mut is_defect: Vec<bool> = (0..initializer.vertex_num).map(|_| false).collect();
118    for &virtual_vertex in initializer.virtual_vertices.iter() {
119        assert!(virtual_vertex < initializer.vertex_num, "invalid input");
120        assert!(!is_virtual[virtual_vertex as usize], "same virtual vertex appears twice");
121        is_virtual[virtual_vertex as usize] = true;
122    }
123    let mut mapping_to_defect_vertices: Vec<usize> = (0..initializer.vertex_num).map(|_| usize::MAX).collect();
124    for (i, &defect_vertex) in defect_vertices.iter().enumerate() {
125        assert!(defect_vertex < initializer.vertex_num, "invalid input");
126        assert!(!is_virtual[defect_vertex as usize], "syndrome vertex cannot be virtual");
127        assert!(!is_defect[defect_vertex as usize], "same syndrome vertex appears twice");
128        is_defect[defect_vertex as usize] = true;
129        mapping_to_defect_vertices[defect_vertex as usize] = i;
130    }
131    // for each real vertex, add a corresponding virtual vertex to be matched
132    let defect_num = defect_vertices.len();
133    let legacy_vertex_num = defect_num * 2;
134    let mut legacy_weighted_edges = Vec::<(usize, usize, u32)>::new();
135    let mut boundaries = Vec::<Option<(VertexIndex, Weight)>>::new();
136    for (i, &defect_vertex) in defect_vertices.iter().enumerate() {
137        let complete_graph_edges = complete_graph.all_edges(defect_vertex);
138        let mut boundary: Option<(VertexIndex, Weight)> = None;
139        for (&peer, &(_, weight)) in complete_graph_edges.iter() {
140            if is_virtual[peer as usize] && (boundary.is_none() || weight < boundary.as_ref().unwrap().1) {
141                boundary = Some((peer, weight));
142            }
143        }
144        if let Some((_, weight)) = boundary {
145            // connect this real vertex to it's corresponding virtual vertex
146            legacy_weighted_edges.push((i, i + defect_num, weight as u32));
147        }
148        boundaries.push(boundary); // save for later resolve legacy matchings
149        for (&peer, &(_, weight)) in complete_graph_edges.iter() {
150            if is_defect[peer as usize] {
151                let j = mapping_to_defect_vertices[peer as usize];
152                if i < j {
153                    // remove duplicated edges
154                    legacy_weighted_edges.push((i, j, weight as u32));
155                    // println!{"edge {} {} {} ", i, j, weight};
156                }
157            }
158        }
159        for j in (i + 1)..defect_num {
160            // virtual boundaries are always fully connected with weight 0
161            legacy_weighted_edges.push((i + defect_num, j + defect_num, 0));
162        }
163    }
164    // run blossom V to get matchings
165    // println!("[debug] legacy_vertex_num: {:?}", legacy_vertex_num);
166    // println!("[debug] legacy_weighted_edges: {:?}", legacy_weighted_edges);
167    let matchings = blossom_v::safe_minimum_weight_perfect_matching(legacy_vertex_num, &legacy_weighted_edges);
168    let mut mwpm_result = Vec::new();
169    for i in 0..defect_num {
170        let j = matchings[i];
171        if j < defect_num {
172            // match to a real vertex
173            mwpm_result.push(defect_vertices[j]);
174        } else {
175            assert_eq!(
176                j,
177                i + defect_num,
178                "if not matched to another real vertex, it must match to it's corresponding virtual vertex"
179            );
180            mwpm_result.push(
181                boundaries[i]
182                    .as_ref()
183                    .expect("boundary must exist if match to virtual vertex")
184                    .0,
185            );
186        }
187    }
188    mwpm_result
189}
190
191#[allow(dead_code)]
192#[derive(Debug, Clone)]
193pub struct DetailedMatching {
194    /// must be a real vertex
195    pub a: DefectIndex,
196    /// might be a virtual vertex, but if it's a real vertex, then b > a stands
197    pub b: DefectIndex,
198    /// every vertex in between this pair, in the order `a -> path[0].0 -> path[1].0 -> .... -> path[-1].0` and it's guaranteed that path[-1].0 = b; might be empty if a and b are adjacent
199    pub path: Vec<(DefectIndex, Weight)>,
200    /// the overall weight of this path
201    pub weight: Weight,
202}
203
204/// compute detailed matching information, note that the output will not include duplicated matched pairs
205#[allow(clippy::unnecessary_cast)]
206pub fn detailed_matching(
207    initializer: &SolverInitializer,
208    defect_vertices: &[DefectIndex],
209    mwpm_result: &[DefectIndex],
210) -> Vec<DetailedMatching> {
211    let defect_num = defect_vertices.len();
212    let mut is_defect: Vec<bool> = (0..initializer.vertex_num).map(|_| false).collect();
213    for &defect_vertex in defect_vertices.iter() {
214        assert!(defect_vertex < initializer.vertex_num, "invalid input");
215        assert!(!is_defect[defect_vertex as usize], "same syndrome vertex appears twice");
216        is_defect[defect_vertex as usize] = true;
217    }
218    assert_eq!(defect_num, mwpm_result.len(), "invalid mwpm result");
219    let mut complete_graph = complete_graph::CompleteGraph::new(initializer.vertex_num, &initializer.weighted_edges);
220    let mut details = Vec::new();
221    for i in 0..defect_num {
222        let a = defect_vertices[i];
223        let b = mwpm_result[i];
224        if !is_defect[b as usize] || a < b {
225            let (path, weight) = complete_graph.get_path(a, b);
226            let detail = DetailedMatching { a, b, path, weight };
227            details.push(detail);
228        }
229    }
230    details
231}
232
233#[cfg(feature = "python_binding")]
234macro_rules! include_visualize_file {
235    ($mapping:ident, $filepath:expr) => {
236        $mapping.insert($filepath.to_string(), include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/visualize/", $filepath)).to_string());
237    };
238    ($mapping:ident, $filepath:expr, $($other_filepath:expr),+) => {
239        include_visualize_file!($mapping, $filepath);
240        include_visualize_file!($mapping, $($other_filepath),+);
241    };
242}
243
244#[cfg(feature = "python_binding")]
245fn generate_visualizer_website(py: Python<'_>) -> &pyo3::types::PyDict {
246    use pyo3::types::IntoPyDict;
247    let mut mapping = std::collections::BTreeMap::<String, String>::new();
248    include_visualize_file!(
249        mapping,
250        "gui3d.js",
251        "index.js",
252        "patches.js",
253        "primal.js",
254        "cmd.js",
255        "mocker.js"
256    );
257    include_visualize_file!(mapping, "index.html", "partition-profile.html", "icon.svg");
258    include_visualize_file!(mapping, "package.json", "package-lock.json");
259    mapping.into_py_dict(py)
260}