fusion_blossom/
cli.rs

1use super::dual_module::*;
2use super::example_codes::*;
3use super::example_partition;
4use super::mwpm_solver::*;
5use super::primal_module::*;
6use super::util::*;
7use super::visualize::*;
8#[cfg(feature = "qecp_integrate")]
9use crate::qecp;
10use clap::{Parser, Subcommand, ValueEnum};
11use derivative::Derivative;
12use pbr::ProgressBar;
13use rand::{thread_rng, Rng};
14use serde::Serialize;
15use serde_json::json;
16use std::env;
17
18const TEST_EACH_ROUNDS: usize = 100;
19
20#[derive(Parser, Clone, Debug)]
21#[clap(author = clap::crate_authors!(", "))]
22#[clap(version = env!("CARGO_PKG_VERSION"))]
23#[clap(about = "Fusion Blossom Algorithm for fast Quantum Error Correction Decoding")]
24#[clap(color = clap::ColorChoice::Auto)]
25#[clap(propagate_version = true)]
26#[clap(subcommand_required = true)]
27#[clap(arg_required_else_help = true)]
28pub struct Cli {
29    #[clap(subcommand)]
30    pub command: Commands,
31}
32
33#[derive(Parser, Clone, Debug)]
34pub struct BenchmarkParameters {
35    /// code distance
36    #[clap(value_parser)]
37    pub d: VertexNum,
38    /// physical error rate: the probability of each edge to
39    #[clap(value_parser)]
40    pub p: f64,
41    /// rounds of noisy measurement, valid only when multiple rounds
42    #[clap(short = 'e', long, default_value_t = 0.)]
43    pub pe: f64,
44    /// rounds of noisy measurement, valid only when multiple rounds
45    #[clap(short = 'n', long, default_value_t = 0)]
46    pub noisy_measurements: VertexNum,
47    /// maximum half weight of edges
48    #[clap(long, default_value_t = 500)]
49    pub max_half_weight: Weight,
50    /// example code type
51    #[clap(short = 'c', long, value_enum, default_value_t = ExampleCodeType::CodeCapacityPlanarCode)]
52    pub code_type: ExampleCodeType,
53    /// the configuration of the code builder
54    #[clap(long, default_value_t = ("{}").to_string())]
55    pub code_config: String,
56    /// logging to the default visualizer file at visualize/data/visualizer.json
57    #[clap(long, action)]
58    pub enable_visualizer: bool,
59    /// visualizer file at visualize/data/<visualizer_filename.json>
60    #[clap(long, default_value_t = crate::visualize::static_visualize_data_filename())]
61    pub visualizer_filename: String,
62    /// print syndrome patterns
63    #[clap(long, action)]
64    pub print_syndrome_pattern: bool,
65    /// the method to verify the correctness of the decoding result
66    #[clap(long, value_enum, default_value_t = Verifier::BlossomV)]
67    pub verifier: Verifier,
68    /// the number of iterations to run
69    #[clap(short = 'r', long, default_value_t = 1000)]
70    pub total_rounds: usize,
71    /// select the combination of primal and dual module
72    #[clap(short = 'p', long, value_enum, default_value_t = PrimalDualType::Serial)]
73    pub primal_dual_type: PrimalDualType,
74    /// the configuration of primal and dual module
75    #[clap(long, default_value_t = ("{}").to_string())]
76    pub primal_dual_config: String,
77    /// partition strategy
78    #[clap(long, value_enum, default_value_t = PartitionStrategy::None)]
79    pub partition_strategy: PartitionStrategy,
80    /// the configuration of the partition strategy
81    #[clap(long, default_value_t = ("{}").to_string())]
82    pub partition_config: String,
83    /// message on the progress bar
84    #[clap(long, default_value_t = format!(""))]
85    pub pb_message: String,
86    /// use deterministic seed for debugging purpose
87    #[clap(long, action)]
88    pub use_deterministic_seed: bool,
89    /// the benchmark profile output file path
90    #[clap(long)]
91    pub benchmark_profiler_output: Option<String>,
92    /// skip some iterations, useful when debugging
93    #[clap(long, default_value_t = 0)]
94    pub starting_iteration: usize,
95}
96
97#[derive(Subcommand, Clone, Derivative)]
98#[allow(clippy::large_enum_variant)]
99#[derivative(Debug)]
100pub enum Commands {
101    /// benchmark the speed (and also correctness if enabled)
102    Benchmark(BenchmarkParameters),
103    #[cfg(feature = "qecp_integrate")]
104    Qecp(qecp::cli::BenchmarkParameters),
105    /// built-in tests
106    Test {
107        #[clap(subcommand)]
108        command: TestCommands,
109    },
110    /// visualize a syndrome graph
111    VisualizeSyndromes(VisualizeSyndromesParameters),
112}
113
114#[derive(Parser, Clone, Debug)]
115pub struct VisualizeSyndromesParameters {
116    /// the syndromes file (generated by qecp using `--fusion-blossom-syndrome-export-filename`)
117    #[clap(value_parser)]
118    pub filepath: String,
119    /// visualizer file at visualize/data/<visualizer_filename.json>
120    #[clap(long, default_value_t = crate::visualize::static_visualize_data_filename())]
121    pub visualizer_filename: String,
122}
123
124#[derive(Subcommand, Clone, Debug)]
125pub enum TestCommands {
126    /// test serial implementation
127    Serial {
128        /// print out the command to test
129        #[clap(short = 'c', long, action)]
130        print_command: bool,
131        /// enable visualizer
132        #[clap(short = 'v', long, action)]
133        enable_visualizer: bool,
134        /// enable the blossom verifier
135        #[clap(short = 'd', long, action)]
136        disable_blossom: bool,
137        /// enable print syndrome pattern
138        #[clap(short = 's', long, action)]
139        print_syndrome_pattern: bool,
140    },
141    /// test parallel dual module only, with serial primal module
142    DualParallel {
143        /// print out the command to test
144        #[clap(short = 'c', long, action)]
145        print_command: bool,
146        /// enable visualizer
147        #[clap(short = 'v', long, action)]
148        enable_visualizer: bool,
149        /// enable the blossom verifier
150        #[clap(short = 'd', long, action)]
151        disable_blossom: bool,
152        /// enable print syndrome pattern
153        #[clap(short = 's', long, action)]
154        print_syndrome_pattern: bool,
155    },
156    /// test parallel primal and dual module
157    Parallel {
158        /// print out the command to test
159        #[clap(short = 'c', long, action)]
160        print_command: bool,
161        /// enable visualizer
162        #[clap(short = 'v', long, action)]
163        enable_visualizer: bool,
164        /// enable the blossom verifier
165        #[clap(short = 'd', long, action)]
166        disable_blossom: bool,
167        /// enable print syndrome pattern
168        #[clap(short = 's', long, action)]
169        print_syndrome_pattern: bool,
170    },
171}
172
173/// note that these code type is only for example, to test and demonstrate the correctness of the algorithm, but not for real QEC simulation;
174/// for real simulation, please refer to <https://github.com/yuewuo/QEC-Playground>
175#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Debug)]
176#[serde(rename_all = "kebab-case")]
177pub enum ExampleCodeType {
178    /// quantum repetition code with perfect stabilizer measurement
179    CodeCapacityRepetitionCode,
180    /// quantum repetition code with phenomenological noise model
181    PhenomenologicalRepetitionCode,
182    /// quantum repetition code with circuit-level noise model
183    CircuitLevelRepetitionCode,
184    /// planar surface code with perfect stabilizer measurement
185    CodeCapacityPlanarCode,
186    /// planar surface code with phenomenological noise model
187    PhenomenologicalPlanarCode,
188    /// parallel version
189    PhenomenologicalPlanarCodeParallel,
190    /// planar surface code with circuit-level noise model
191    CircuitLevelPlanarCode,
192    /// parallel version
193    CircuitLevelPlanarCodeParallel,
194    /// read from error pattern file, generated using option `--primal-dual-type error-pattern-logger`
195    ErrorPatternReader,
196    /// rotated surface code with perfect stabilizer measurement
197    CodeCapacityRotatedCode,
198    /// rotated surface code with phenomenological noise model
199    PhenomenologicalRotatedCode,
200    /// code constructed by QEC-Playground, pass configurations using `--code-config`
201    #[serde(rename = "qec-playground-code")]
202    QECPlaygroundCode,
203}
204
205#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Debug)]
206pub enum PartitionStrategy {
207    /// no partition
208    None,
209    /// partition a planar code into top half and bottom half
210    CodeCapacityPlanarCodeVerticalPartitionHalf,
211    /// partition a planar code into 4 pieces: top left and right, bottom left and right
212    CodeCapacityPlanarCodeVerticalPartitionFour,
213    /// partition a repetition code into left and right half
214    CodeCapacityRepetitionCodePartitionHalf,
215    /// partition a phenomenological (or circuit-level) planar code with time axis
216    PhenomenologicalPlanarCodeTimePartition,
217    /// partition a phenomenological (or circuit-level) rotated code with time axis
218    PhenomenologicalRotatedCodeTimePartition,
219}
220
221#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Debug)]
222pub enum PrimalDualType {
223    /// serial primal and dual
224    Serial,
225    /// parallel dual and serial primal
226    DualParallel,
227    /// parallel primal and dual
228    Parallel,
229    /// log error into a file for later fetch
230    ErrorPatternLogger,
231    /// solver using traditional blossom V
232    BlossomV,
233}
234
235#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Debug)]
236pub enum Verifier {
237    /// disable verifier
238    None,
239    /// use blossom V library to verify the correctness of result
240    BlossomV,
241    /// use the serial version of fusion algorithm to verify the correctness of result
242    FusionSerial,
243}
244
245pub struct RunnableBenchmarkParameters {
246    pub code: Box<dyn ExampleCode>,
247    pub primal_dual_solver: Box<dyn PrimalDualSolver>,
248    pub result_verifier: Box<dyn ResultVerifier>,
249    pub benchmark_profiler: BenchmarkProfiler,
250    pub parameters: BenchmarkParameters,
251}
252
253impl From<BenchmarkParameters> for RunnableBenchmarkParameters {
254    fn from(parameters: BenchmarkParameters) -> Self {
255        let BenchmarkParameters {
256            d,
257            p,
258            pe,
259            noisy_measurements,
260            max_half_weight,
261            code_type,
262            enable_visualizer,
263            visualizer_filename,
264            verifier,
265            primal_dual_type,
266            partition_strategy,
267            primal_dual_config,
268            code_config,
269            partition_config,
270            benchmark_profiler_output,
271            ..
272        } = parameters.clone();
273        let code_config: serde_json::Value = serde_json::from_str(&code_config).unwrap();
274        let primal_dual_config: serde_json::Value = serde_json::from_str(&primal_dual_config).unwrap();
275        let partition_config: serde_json::Value = serde_json::from_str(&partition_config).unwrap();
276        // check for dependency early
277        if matches!(verifier, Verifier::BlossomV) && cfg!(not(feature = "blossom_v")) {
278            panic!("need blossom V library, see README.md")
279        }
280        let mut code: Box<dyn ExampleCode> = code_type.build(d, p, noisy_measurements, max_half_weight, code_config);
281        if pe != 0. {
282            code.set_erasure_probability(pe);
283        }
284        if enable_visualizer {
285            // print visualizer file path only once
286            print_visualize_link(visualizer_filename.clone());
287        }
288        // create initializer and solver
289        let (initializer, partition_config) = partition_strategy.build(&mut *code, d, noisy_measurements, partition_config);
290        let partition_info = partition_config.info();
291        let primal_dual_solver = primal_dual_type.build(&initializer, &partition_info, &*code, primal_dual_config);
292        let benchmark_profiler =
293            BenchmarkProfiler::new(noisy_measurements, benchmark_profiler_output.map(|x| (x, &partition_info)));
294        let result_verifier = verifier.build(&initializer);
295        Self {
296            code,
297            primal_dual_solver,
298            result_verifier,
299            benchmark_profiler,
300            parameters,
301        }
302    }
303}
304
305impl RunnableBenchmarkParameters {
306    pub fn run(self) {
307        let Self {
308            mut code,
309            mut primal_dual_solver,
310            mut result_verifier,
311            mut benchmark_profiler,
312            parameters:
313                BenchmarkParameters {
314                    starting_iteration,
315                    total_rounds,
316                    use_deterministic_seed,
317                    print_syndrome_pattern,
318                    pb_message,
319                    enable_visualizer,
320                    visualizer_filename,
321                    ..
322                },
323        } = self;
324        // whether to disable progress bar, useful when running jobs in background
325        let disable_progress_bar = env::var("DISABLE_PROGRESS_BAR").is_ok();
326        // prepare progress bar display
327        let mut pb = if !disable_progress_bar {
328            let mut pb = ProgressBar::on(std::io::stderr(), total_rounds as u64);
329            pb.message(format!("{pb_message} ").as_str());
330            Some(pb)
331        } else {
332            if !pb_message.is_empty() {
333                print!("{pb_message} ");
334            }
335            None
336        };
337        let mut rng = thread_rng();
338        // share the same visualizer across all rounds
339        let mut visualizer = None;
340        if enable_visualizer {
341            let new_visualizer = Visualizer::new(
342                Some(visualize_data_folder() + visualizer_filename.as_str()),
343                code.get_positions(),
344                true,
345            )
346            .unwrap();
347            visualizer = Some(new_visualizer);
348        }
349        for round in (starting_iteration as u64)..(total_rounds as u64) {
350            pb.as_mut().map(|pb| pb.set(round));
351            let seed = if use_deterministic_seed { round } else { rng.gen() };
352            let syndrome_pattern = code.generate_random_errors(seed);
353            if print_syndrome_pattern {
354                println!("syndrome_pattern: {:?}", syndrome_pattern);
355            }
356            benchmark_profiler.begin(&syndrome_pattern);
357            primal_dual_solver.solve_visualizer(&syndrome_pattern, visualizer.as_mut());
358            benchmark_profiler.event("decoded".to_string());
359            result_verifier.verify(&mut primal_dual_solver, &syndrome_pattern, visualizer.as_mut());
360            benchmark_profiler.event("verified".to_string());
361            primal_dual_solver.clear(); // also count the clear operation
362            benchmark_profiler.end(Some(&*primal_dual_solver));
363            primal_dual_solver.reset_profiler();
364            if let Some(pb) = pb.as_mut() {
365                if pb_message.is_empty() {
366                    pb.message(format!("{} ", benchmark_profiler.brief()).as_str());
367                }
368            }
369        }
370        if disable_progress_bar {
371            // always print out brief
372            println!("{}", benchmark_profiler.brief());
373        } else {
374            if let Some(pb) = pb.as_mut() {
375                pb.finish()
376            }
377            println!();
378        }
379    }
380}
381
382impl Cli {
383    pub fn run(self) {
384        match self.command {
385            Commands::Benchmark(benchmark_parameters) => {
386                let runnable = RunnableBenchmarkParameters::from(benchmark_parameters);
387                runnable.run();
388            }
389            Commands::VisualizeSyndromes(parameters) => {
390                let code_config = json!({
391                    "filename": parameters.filepath
392                });
393                let reader = ErrorPatternReader::new(code_config.clone());
394                let code_config_str = serde_json::to_string(&code_config).unwrap();
395                let total_rounds_str = format!("{}", reader.syndrome_patterns.len());
396                drop(reader);
397                let command: Vec<String> = [
398                    "",
399                    "benchmark",
400                    "0",
401                    "0",
402                    "--code-type",
403                    "error-pattern-reader",
404                    "--code-config",
405                    code_config_str.as_str(),
406                    "--verifier",
407                    "none",
408                    "--total-rounds",
409                    total_rounds_str.as_str(),
410                    "--enable-visualizer",
411                    "--visualizer-filename",
412                    parameters.visualizer_filename.as_str(),
413                ]
414                .into_iter()
415                .map(|s| s.to_string())
416                .collect();
417                execute_in_cli(command.iter(), true);
418            }
419            Commands::Test { command } => {
420                match command {
421                    TestCommands::Serial {
422                        print_command,
423                        enable_visualizer,
424                        disable_blossom,
425                        print_syndrome_pattern,
426                    } => {
427                        let mut parameters = vec![];
428                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
429                            for d in [3, 7, 11, 15, 19] {
430                                parameters.push(vec![
431                                    format!("{d}"),
432                                    format!("{p}"),
433                                    format!("--code-type"),
434                                    format!("code-capacity-repetition-code"),
435                                    format!("--pb-message"),
436                                    format!("repetition {d} {p}"),
437                                ]);
438                            }
439                        }
440                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
441                            for d in [3, 7, 11, 15, 19] {
442                                parameters.push(vec![
443                                    format!("{d}"),
444                                    format!("{p}"),
445                                    format!("--code-type"),
446                                    format!("code-capacity-planar-code"),
447                                    format!("--pb-message"),
448                                    format!("planar {d} {p}"),
449                                ]);
450                            }
451                        }
452                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
453                            // test erasures
454                            for d in [3, 7, 11, 15, 19] {
455                                parameters.push(vec![
456                                    format!("{d}"),
457                                    format!("{p}"),
458                                    format!("--code-type"),
459                                    format!("code-capacity-planar-code"),
460                                    format!("--pe"),
461                                    format!("{p}"),
462                                    format!("--pb-message"),
463                                    format!("mixed erasure planar {d} {p}"),
464                                ]);
465                            }
466                        }
467                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
468                            for d in [3, 7, 11] {
469                                parameters.push(vec![
470                                    format!("{d}"),
471                                    format!("{p}"),
472                                    format!("--code-type"),
473                                    format!("phenomenological-planar-code"),
474                                    format!("--noisy-measurements"),
475                                    format!("{d}"),
476                                    format!("--pb-message"),
477                                    format!("phenomenological {d} {p}"),
478                                ]);
479                            }
480                        }
481                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
482                            for d in [3, 7, 11] {
483                                parameters.push(vec![
484                                    format!("{d}"),
485                                    format!("{p}"),
486                                    format!("--code-type"),
487                                    format!("circuit-level-planar-code"),
488                                    format!("--noisy-measurements"),
489                                    format!("{d}"),
490                                    format!("--pb-message"),
491                                    format!("circuit-level {d} {p}"),
492                                ]);
493                            }
494                        }
495                        let command_head = [String::new(), "benchmark".to_string()];
496                        let mut command_tail = vec!["--total-rounds".to_string(), format!("{TEST_EACH_ROUNDS}")];
497                        if !disable_blossom {
498                            command_tail.append(&mut vec![format!("--verifier"), format!("blossom-v")]);
499                        } else {
500                            command_tail.append(&mut vec![format!("--verifier"), format!("none")]);
501                        }
502                        if enable_visualizer {
503                            command_tail.append(&mut vec![format!("--enable-visualizer")]);
504                        }
505                        if print_syndrome_pattern {
506                            command_tail.append(&mut vec![format!("--print-syndrome-pattern")]);
507                        }
508                        for parameter in parameters.iter() {
509                            execute_in_cli(
510                                command_head.iter().chain(parameter.iter()).chain(command_tail.iter()),
511                                print_command,
512                            );
513                        }
514                    }
515                    TestCommands::DualParallel {
516                        print_command,
517                        enable_visualizer,
518                        disable_blossom,
519                        print_syndrome_pattern,
520                    } => {
521                        let mut parameters = vec![];
522                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
523                            for d in [7, 11, 15, 19] {
524                                parameters.push(vec![
525                                    format!("{d}"),
526                                    format!("{p}"),
527                                    format!("--code-type"),
528                                    format!("code-capacity-repetition-code"),
529                                    format!("--partition-strategy"),
530                                    format!("code-capacity-repetition-code-partition-half"),
531                                    format!("--pb-message"),
532                                    format!("dual-parallel 2-partition repetition {d} {p}"),
533                                ]);
534                            }
535                        }
536                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
537                            // simple partition into top and bottom
538                            for d in [7, 11, 15, 19] {
539                                parameters.push(vec![
540                                    format!("{d}"),
541                                    format!("{p}"),
542                                    format!("--code-type"),
543                                    format!("code-capacity-planar-code"),
544                                    format!("--partition-strategy"),
545                                    format!("code-capacity-planar-code-vertical-partition-half"),
546                                    format!("--pb-message"),
547                                    format!("dual-parallel 2-partition planar {d} {p}"),
548                                ]);
549                            }
550                        }
551                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
552                            // complex partition into 4 blocks
553                            for d in [7, 11, 15, 19] {
554                                parameters.push(vec![
555                                    format!("{d}"),
556                                    format!("{p}"),
557                                    format!("--code-type"),
558                                    format!("code-capacity-planar-code"),
559                                    format!("--partition-strategy"),
560                                    format!("code-capacity-planar-code-vertical-partition-four"),
561                                    format!("--pb-message"),
562                                    format!("dual-parallel 4-partition planar {d} {p}"),
563                                ]);
564                            }
565                        }
566                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
567                            for d in [3, 7, 11] {
568                                parameters.push(vec![
569                                    format!("{d}"),
570                                    format!("{p}"),
571                                    format!("--code-type"),
572                                    format!("phenomenological-planar-code"),
573                                    format!("--noisy-measurements"),
574                                    format!("{d}"),
575                                    format!("--partition-strategy"),
576                                    format!("phenomenological-planar-code-time-partition"),
577                                    format!("--partition-config"),
578                                    "{\"partition_num\":2,\"enable_tree_fusion\":true}".to_string(),
579                                    format!("--pb-message"),
580                                    format!("dual-parallel 2-partition phenomenological {d} {d} {p}"),
581                                ]);
582                            }
583                        }
584                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
585                            for d in [3, 7, 11] {
586                                parameters.push(vec![
587                                    format!("{d}"),
588                                    format!("{p}"),
589                                    format!("--code-type"),
590                                    format!("circuit-level-planar-code"),
591                                    format!("--noisy-measurements"),
592                                    format!("{d}"),
593                                    format!("--partition-strategy"),
594                                    format!("phenomenological-planar-code-time-partition"),
595                                    format!("--partition-config"),
596                                    "{\"partition_num\":2,\"enable_tree_fusion\":true}".to_string(),
597                                    format!("--pb-message"),
598                                    format!("dual-parallel 2-partition circuit-level {d} {d} {p}"),
599                                ]);
600                            }
601                        }
602                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
603                            for partition_num in [2, 3, 4, 5, 6, 7, 8, 9, 10] {
604                                // test large number of fusion without tree fusion
605                                let d = 5;
606                                let noisy_measurement = 20;
607                                parameters.push(vec![
608                                    format!("{d}"),
609                                    format!("{p}"),
610                                    format!("--code-type"),
611                                    format!("circuit-level-planar-code"),
612                                    format!("--noisy-measurements"),
613                                    format!("{noisy_measurement}"),
614                                    format!("--partition-strategy"),
615                                    format!("phenomenological-planar-code-time-partition"),
616                                    format!("--partition-config"),
617                                    format!("{{\"partition_num\":{partition_num},\"enable_tree_fusion\":false}}"),
618                                    format!("--pb-message"),
619                                    format!(
620                                        "dual-parallel {partition_num}-partition circuit-level {d} {noisy_measurement} {p}"
621                                    ),
622                                ]);
623                            }
624                        }
625                        let command_head = [String::new(), "benchmark".to_string()];
626                        let mut command_tail = vec![
627                            format!("--primal-dual-type"),
628                            format!("dual-parallel"),
629                            "--total-rounds".to_string(),
630                            format!("{TEST_EACH_ROUNDS}"),
631                        ];
632                        if !disable_blossom {
633                            command_tail.append(&mut vec![format!("--verifier"), format!("blossom-v")]);
634                        } else {
635                            command_tail.append(&mut vec![format!("--verifier"), format!("none")]);
636                        }
637                        if enable_visualizer {
638                            command_tail.append(&mut vec![format!("--enable-visualizer")]);
639                        }
640                        if print_syndrome_pattern {
641                            command_tail.append(&mut vec![format!("--print-syndrome-pattern")]);
642                        }
643                        for parameter in parameters.iter() {
644                            execute_in_cli(
645                                command_head.iter().chain(parameter.iter()).chain(command_tail.iter()),
646                                print_command,
647                            );
648                        }
649                    }
650                    TestCommands::Parallel {
651                        print_command,
652                        enable_visualizer,
653                        disable_blossom,
654                        print_syndrome_pattern,
655                    } => {
656                        let mut parameters = vec![];
657                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
658                            for d in [7, 11, 15, 19] {
659                                parameters.push(vec![
660                                    format!("{d}"),
661                                    format!("{p}"),
662                                    format!("--code-type"),
663                                    format!("code-capacity-repetition-code"),
664                                    format!("--partition-strategy"),
665                                    format!("code-capacity-repetition-code-partition-half"),
666                                    format!("--pb-message"),
667                                    format!("parallel 2-partition repetition {d} {p}"),
668                                ]);
669                            }
670                        }
671                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
672                            // simple partition into top and bottom
673                            for d in [7, 11, 15, 19] {
674                                parameters.push(vec![
675                                    format!("{d}"),
676                                    format!("{p}"),
677                                    format!("--code-type"),
678                                    format!("code-capacity-planar-code"),
679                                    format!("--partition-strategy"),
680                                    format!("code-capacity-planar-code-vertical-partition-half"),
681                                    format!("--pb-message"),
682                                    format!("parallel 2-partition planar {d} {p}"),
683                                ]);
684                            }
685                        }
686                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
687                            // complex partition into 4 blocks
688                            for d in [7, 11, 15, 19] {
689                                parameters.push(vec![
690                                    format!("{d}"),
691                                    format!("{p}"),
692                                    format!("--code-type"),
693                                    format!("code-capacity-planar-code"),
694                                    format!("--partition-strategy"),
695                                    format!("code-capacity-planar-code-vertical-partition-four"),
696                                    format!("--pb-message"),
697                                    format!("parallel 4-partition planar {d} {p}"),
698                                ]);
699                            }
700                        }
701                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
702                            for d in [3, 7, 11] {
703                                parameters.push(vec![
704                                    format!("{d}"),
705                                    format!("{p}"),
706                                    format!("--code-type"),
707                                    format!("phenomenological-planar-code"),
708                                    format!("--noisy-measurements"),
709                                    format!("{d}"),
710                                    format!("--partition-strategy"),
711                                    format!("phenomenological-planar-code-time-partition"),
712                                    format!("--partition-config"),
713                                    "{\"partition_num\":2,\"enable_tree_fusion\":true}".to_string(),
714                                    format!("--pb-message"),
715                                    format!("parallel 2-partition phenomenological {d} {d} {p}"),
716                                ]);
717                            }
718                        }
719                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
720                            for d in [3, 7, 11] {
721                                parameters.push(vec![
722                                    format!("{d}"),
723                                    format!("{p}"),
724                                    format!("--code-type"),
725                                    format!("circuit-level-planar-code"),
726                                    format!("--noisy-measurements"),
727                                    format!("{d}"),
728                                    format!("--partition-strategy"),
729                                    format!("phenomenological-planar-code-time-partition"),
730                                    format!("--partition-config"),
731                                    "{\"partition_num\":2,\"enable_tree_fusion\":true}".to_string(),
732                                    format!("--pb-message"),
733                                    format!("parallel 2-partition circuit-level {d} {d} {p}"),
734                                ]);
735                            }
736                        }
737                        for p in [0.001, 0.003, 0.01, 0.03, 0.1, 0.3, 0.499] {
738                            for partition_num in [2, 3, 4, 5, 6, 7, 8, 9, 10] {
739                                // test large number of fusion without tree fusion
740                                let d = 5;
741                                let noisy_measurement = 20;
742                                parameters.push(vec![
743                                    format!("{d}"),
744                                    format!("{p}"),
745                                    format!("--code-type"),
746                                    format!("circuit-level-planar-code"),
747                                    format!("--noisy-measurements"),
748                                    format!("{noisy_measurement}"),
749                                    format!("--partition-strategy"),
750                                    format!("phenomenological-planar-code-time-partition"),
751                                    format!("--partition-config"),
752                                    format!("{{\"partition_num\":{partition_num},\"enable_tree_fusion\":false}}"),
753                                    format!("--pb-message"),
754                                    format!("parallel {partition_num}-partition circuit-level {d} {noisy_measurement} {p}"),
755                                ]);
756                            }
757                        }
758                        let command_head = [String::new(), "benchmark".to_string()];
759                        let mut command_tail = vec![
760                            format!("--primal-dual-type"),
761                            format!("parallel"),
762                            "--total-rounds".to_string(),
763                            format!("{TEST_EACH_ROUNDS}"),
764                        ];
765                        if !disable_blossom {
766                            command_tail.append(&mut vec![format!("--verifier"), format!("blossom-v")]);
767                        } else {
768                            command_tail.append(&mut vec![format!("--verifier"), format!("none")]);
769                        }
770                        if enable_visualizer {
771                            command_tail.append(&mut vec![format!("--enable-visualizer")]);
772                        }
773                        if print_syndrome_pattern {
774                            command_tail.append(&mut vec![format!("--print-syndrome-pattern")]);
775                        }
776                        for parameter in parameters.iter() {
777                            execute_in_cli(
778                                command_head.iter().chain(parameter.iter()).chain(command_tail.iter()),
779                                print_command,
780                            );
781                        }
782                    }
783                }
784            }
785            #[cfg(feature = "qecp_integrate")]
786            Commands::Qecp(benchmark_parameters) => {
787                println!("{}", benchmark_parameters.run().unwrap());
788            }
789        }
790    }
791}
792
793pub fn execute_in_cli<'a>(iter: impl Iterator<Item = &'a String> + Clone, print_command: bool) {
794    if print_command {
795        print!("[command]");
796        for word in iter.clone() {
797            if word.contains(char::is_whitespace) {
798                print!("'{word}' ")
799            } else {
800                print!("{word} ")
801            }
802        }
803        println!();
804    }
805    Cli::parse_from(iter).run();
806}
807
808impl ExampleCodeType {
809    pub fn build(
810        &self,
811        d: VertexNum,
812        p: f64,
813        noisy_measurements: VertexNum,
814        max_half_weight: Weight,
815        mut code_config: serde_json::Value,
816    ) -> Box<dyn ExampleCode> {
817        match self {
818            Self::CodeCapacityRepetitionCode => {
819                assert_eq!(code_config, json!({}), "config not supported");
820                Box::new(CodeCapacityRepetitionCode::new(d, p, max_half_weight))
821            }
822            Self::CodeCapacityPlanarCode => {
823                assert_eq!(code_config, json!({}), "config not supported");
824                Box::new(CodeCapacityPlanarCode::new(d, p, max_half_weight))
825            }
826            Self::PhenomenologicalPlanarCode => {
827                assert_eq!(code_config, json!({}), "config not supported");
828                Box::new(PhenomenologicalPlanarCode::new(d, noisy_measurements, p, max_half_weight))
829            }
830            Self::PhenomenologicalPlanarCodeParallel => {
831                let mut code_count = 1;
832                let config = code_config.as_object_mut().expect("config must be JSON object");
833                if let Some(value) = config.remove("code_count") {
834                    code_count = value.as_u64().expect("code_count number") as usize;
835                }
836                Box::new(ExampleCodeParallel::new(
837                    PhenomenologicalPlanarCode::new(d, noisy_measurements, p, max_half_weight),
838                    code_count,
839                ))
840            }
841            Self::CircuitLevelPlanarCode => {
842                assert_eq!(code_config, json!({}), "config not supported");
843                Box::new(CircuitLevelPlanarCode::new(d, noisy_measurements, p, max_half_weight))
844            }
845            Self::CircuitLevelPlanarCodeParallel => {
846                let mut code_count = 1;
847                let config = code_config.as_object_mut().expect("config must be JSON object");
848                if let Some(value) = config.remove("code_count") {
849                    code_count = value.as_u64().expect("code_count number") as usize;
850                }
851                Box::new(ExampleCodeParallel::new(
852                    CircuitLevelPlanarCode::new(d, noisy_measurements, p, max_half_weight),
853                    code_count,
854                ))
855            }
856            Self::ErrorPatternReader => Box::new(ErrorPatternReader::new(code_config)),
857            Self::CodeCapacityRotatedCode => {
858                assert_eq!(code_config, json!({}), "config not supported");
859                Box::new(CodeCapacityRotatedCode::new(d, p, max_half_weight))
860            }
861            Self::PhenomenologicalRotatedCode => {
862                assert_eq!(code_config, json!({}), "config not supported");
863                Box::new(PhenomenologicalRotatedCode::new(d, noisy_measurements, p, max_half_weight))
864            }
865            #[cfg(feature = "qecp_integrate")]
866            Self::QECPlaygroundCode => Box::new(QECPlaygroundCode::new(d as usize, p, code_config)),
867            _ => unimplemented!(),
868        }
869    }
870}
871
872impl PartitionStrategy {
873    pub fn build(
874        &self,
875        code: &mut dyn ExampleCode,
876        d: VertexNum,
877        noisy_measurements: VertexNum,
878        mut partition_config: serde_json::Value,
879    ) -> (SolverInitializer, PartitionConfig) {
880        use example_partition::*;
881        let partition_config = match self {
882            Self::None => {
883                assert_eq!(partition_config, json!({}), "config not supported");
884                NoPartition::new().build_apply(code)
885            }
886            Self::CodeCapacityPlanarCodeVerticalPartitionHalf => {
887                assert_eq!(partition_config, json!({}), "config not supported");
888                CodeCapacityPlanarCodeVerticalPartitionHalf::new(d, d / 2).build_apply(code)
889            }
890            Self::CodeCapacityPlanarCodeVerticalPartitionFour => {
891                assert_eq!(partition_config, json!({}), "config not supported");
892                CodeCapacityPlanarCodeVerticalPartitionFour::new(d, d / 2, d / 2).build_apply(code)
893            }
894            Self::CodeCapacityRepetitionCodePartitionHalf => {
895                assert_eq!(partition_config, json!({}), "config not supported");
896                CodeCapacityRepetitionCodePartitionHalf::new(d, d / 2).build_apply(code)
897            }
898            Self::PhenomenologicalPlanarCodeTimePartition => {
899                let config = partition_config.as_object_mut().expect("config must be JSON object");
900                let mut partition_num = 10;
901                let mut enable_tree_fusion = false;
902                let mut maximum_tree_leaf_size = usize::MAX;
903                if let Some(value) = config.remove("partition_num") {
904                    partition_num = value.as_u64().expect("partition_num: usize") as usize;
905                }
906                if let Some(value) = config.remove("enable_tree_fusion") {
907                    enable_tree_fusion = value.as_bool().expect("enable_tree_fusion: bool");
908                }
909                if let Some(value) = config.remove("maximum_tree_leaf_size") {
910                    maximum_tree_leaf_size = value.as_u64().expect("maximum_tree_leaf_size: usize") as usize;
911                }
912                if !config.is_empty() {
913                    panic!("unknown config keys: {:?}", config.keys().collect::<Vec<&String>>());
914                }
915                PhenomenologicalPlanarCodeTimePartition::new_tree(
916                    d,
917                    noisy_measurements,
918                    partition_num,
919                    enable_tree_fusion,
920                    maximum_tree_leaf_size,
921                )
922                .build_apply(code)
923            }
924            Self::PhenomenologicalRotatedCodeTimePartition => {
925                let config = partition_config.as_object_mut().expect("config must be JSON object");
926                let mut partition_num = 10;
927                let mut enable_tree_fusion = false;
928                let mut maximum_tree_leaf_size = usize::MAX;
929                if let Some(value) = config.remove("partition_num") {
930                    partition_num = value.as_u64().expect("partition_num: usize") as usize;
931                }
932                if let Some(value) = config.remove("enable_tree_fusion") {
933                    enable_tree_fusion = value.as_bool().expect("enable_tree_fusion: bool");
934                }
935                if let Some(value) = config.remove("maximum_tree_leaf_size") {
936                    maximum_tree_leaf_size = value.as_u64().expect("maximum_tree_leaf_size: usize") as usize;
937                }
938                if !config.is_empty() {
939                    panic!("unknown config keys: {:?}", config.keys().collect::<Vec<&String>>());
940                }
941                PhenomenologicalRotatedCodeTimePartition::new_tree(
942                    d,
943                    noisy_measurements,
944                    partition_num,
945                    enable_tree_fusion,
946                    maximum_tree_leaf_size,
947                )
948                .build_apply(code)
949            }
950        };
951        (code.get_initializer(), partition_config)
952    }
953}
954
955impl PrimalDualType {
956    pub fn build(
957        &self,
958        initializer: &SolverInitializer,
959        partition_info: &PartitionInfo,
960        code: &dyn ExampleCode,
961        primal_dual_config: serde_json::Value,
962    ) -> Box<dyn PrimalDualSolver> {
963        match self {
964            Self::Serial => {
965                assert_eq!(primal_dual_config, json!({}));
966                assert_eq!(
967                    partition_info.config.partitions.len(),
968                    1,
969                    "no partition is supported by serial algorithm, consider using other primal-dual-type"
970                );
971                Box::new(SolverSerial::new(initializer))
972            }
973            Self::DualParallel => Box::new(SolverDualParallel::new(initializer, partition_info, primal_dual_config)),
974            Self::Parallel => Box::new(SolverParallel::new(initializer, partition_info, primal_dual_config)),
975            Self::ErrorPatternLogger => Box::new(SolverErrorPatternLogger::new(
976                initializer,
977                &code.get_positions(),
978                primal_dual_config,
979            )),
980            Self::BlossomV => Box::new(SolverBlossomV::new(initializer)),
981        }
982    }
983}
984
985impl Verifier {
986    pub fn build(&self, initializer: &SolverInitializer) -> Box<dyn ResultVerifier> {
987        match self {
988            Self::None => Box::new(VerifierNone {}),
989            Self::BlossomV => Box::new(VerifierBlossomV {
990                initializer: initializer.clone(),
991                subgraph_builder: SubGraphBuilder::new(initializer),
992            }),
993            Self::FusionSerial => Box::new(VerifierFusionSerial::new(initializer)),
994        }
995    }
996}
997
998pub trait ResultVerifier {
999    fn verify(
1000        &mut self,
1001        primal_dual_solver: &mut Box<dyn PrimalDualSolver>,
1002        syndrome_pattern: &SyndromePattern,
1003        visualizer: Option<&mut Visualizer>,
1004    );
1005}
1006
1007pub struct VerifierNone {}
1008
1009impl ResultVerifier for VerifierNone {
1010    fn verify(
1011        &mut self,
1012        primal_dual_solver: &mut Box<dyn PrimalDualSolver>,
1013        _syndrome_pattern: &SyndromePattern,
1014        visualizer: Option<&mut Visualizer>,
1015    ) {
1016        if visualizer.is_some() {
1017            primal_dual_solver.subgraph_visualizer(visualizer);
1018        }
1019    }
1020}
1021
1022pub struct VerifierBlossomV {
1023    initializer: SolverInitializer,
1024    subgraph_builder: SubGraphBuilder,
1025}
1026
1027pub fn get_primal_dual_solver_total_weight(
1028    primal_dual_solver: &mut Box<dyn PrimalDualSolver>,
1029    syndrome_pattern: &SyndromePattern,
1030    initializer: &SolverInitializer,
1031) -> (PerfectMatching, Weight) {
1032    let mwpm = primal_dual_solver.perfect_matching();
1033    let legacy_mwpm = mwpm.legacy_get_mwpm_result(syndrome_pattern.defect_vertices.clone());
1034    let fusion_details = super::detailed_matching(initializer, &syndrome_pattern.defect_vertices, &legacy_mwpm);
1035    let mut total_weight = 0;
1036    for detail in fusion_details.iter() {
1037        total_weight += detail.weight;
1038    }
1039    (mwpm, total_weight)
1040}
1041
1042impl ResultVerifier for VerifierBlossomV {
1043    #[allow(clippy::unnecessary_cast)]
1044    fn verify(
1045        &mut self,
1046        primal_dual_solver: &mut Box<dyn PrimalDualSolver>,
1047        syndrome_pattern: &SyndromePattern,
1048        visualizer: Option<&mut Visualizer>,
1049    ) {
1050        // prepare modified weighted edges
1051        let mut edge_modifier = EdgeWeightModifier::new();
1052        for edge_index in syndrome_pattern.erasures.iter() {
1053            let (vertex_idx_1, vertex_idx_2, original_weight) = &self.initializer.weighted_edges[*edge_index as usize];
1054            edge_modifier.push_modified_edge(*edge_index, *original_weight);
1055            self.initializer.weighted_edges[*edge_index as usize] = (*vertex_idx_1, *vertex_idx_2, 0);
1056        }
1057        // use blossom V to compute ground truth
1058        let blossom_mwpm_result = super::blossom_v_mwpm(&self.initializer, &syndrome_pattern.defect_vertices);
1059        let blossom_details =
1060            super::detailed_matching(&self.initializer, &syndrome_pattern.defect_vertices, &blossom_mwpm_result);
1061        let mut blossom_total_weight = 0;
1062        for detail in blossom_details.iter() {
1063            blossom_total_weight += detail.weight;
1064        }
1065        // if blossom_total_weight > 0 { println!("w {} {}", primal_dual_solver.sum_dual_variables(), blossom_total_weight); }
1066        assert_eq!(
1067            primal_dual_solver.sum_dual_variables(),
1068            blossom_total_weight,
1069            "unexpected final dual variable sum"
1070        );
1071        // also construct the perfect matching from fusion blossom to compare them
1072        let (fusion_mwpm, fusion_total_weight) =
1073            get_primal_dual_solver_total_weight(primal_dual_solver, syndrome_pattern, &self.initializer);
1074        // compare with ground truth from the blossom V algorithm
1075        assert_eq!(
1076            fusion_total_weight, blossom_total_weight,
1077            "unexpected final dual variable sum"
1078        );
1079        // recover those weighted_edges
1080        while edge_modifier.has_modified_edges() {
1081            let (edge_index, original_weight) = edge_modifier.pop_modified_edge();
1082            let (vertex_idx_1, vertex_idx_2, _) = &self.initializer.weighted_edges[edge_index as usize];
1083            self.initializer.weighted_edges[edge_index as usize] = (*vertex_idx_1, *vertex_idx_2, original_weight);
1084        }
1085        // also test subgraph builder
1086        self.subgraph_builder.clear();
1087        self.subgraph_builder.load_erasures(&syndrome_pattern.erasures);
1088        self.subgraph_builder.load_perfect_matching(&fusion_mwpm);
1089        // println!("blossom_total_weight: {blossom_total_weight} = {} = {fusion_total_weight}", self.subgraph_builder.total_weight());
1090        assert_eq!(
1091            self.subgraph_builder.total_weight(),
1092            blossom_total_weight,
1093            "unexpected final dual variable sum"
1094        );
1095        if visualizer.is_some() {
1096            primal_dual_solver.subgraph_visualizer(visualizer);
1097        }
1098    }
1099}
1100
1101pub struct VerifierFusionSerial {
1102    pub solver: SolverSerial,
1103    pub initializer: SolverInitializer,
1104    pub subgraph_builder: SubGraphBuilder,
1105}
1106
1107impl VerifierFusionSerial {
1108    pub fn new(initializer: &SolverInitializer) -> Self {
1109        Self {
1110            solver: SolverSerial::new(initializer),
1111            initializer: initializer.clone(),
1112            subgraph_builder: SubGraphBuilder::new(initializer),
1113        }
1114    }
1115}
1116
1117impl ResultVerifier for VerifierFusionSerial {
1118    #[allow(clippy::unnecessary_cast)]
1119    fn verify(
1120        &mut self,
1121        primal_dual_solver: &mut Box<dyn PrimalDualSolver>,
1122        syndrome_pattern: &SyndromePattern,
1123        visualizer: Option<&mut Visualizer>,
1124    ) {
1125        self.solver.clear();
1126        self.solver.solve_visualizer(syndrome_pattern, None);
1127        let standard_total_weight = self.solver.sum_dual_variables();
1128        assert_eq!(
1129            primal_dual_solver.sum_dual_variables(),
1130            standard_total_weight,
1131            "unexpected final dual variable sum"
1132        );
1133        self.subgraph_builder.clear();
1134        self.subgraph_builder.load_erasures(&syndrome_pattern.erasures);
1135        let mwpm = primal_dual_solver.perfect_matching();
1136        self.subgraph_builder.load_perfect_matching(&mwpm);
1137        assert_eq!(
1138            self.subgraph_builder.total_weight(),
1139            standard_total_weight,
1140            "unexpected perfect matching weight"
1141        );
1142        if visualizer.is_some() {
1143            primal_dual_solver.subgraph_visualizer(visualizer);
1144        }
1145    }
1146}