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 #[clap(value_parser)]
37 pub d: VertexNum,
38 #[clap(value_parser)]
40 pub p: f64,
41 #[clap(short = 'e', long, default_value_t = 0.)]
43 pub pe: f64,
44 #[clap(short = 'n', long, default_value_t = 0)]
46 pub noisy_measurements: VertexNum,
47 #[clap(long, default_value_t = 500)]
49 pub max_half_weight: Weight,
50 #[clap(short = 'c', long, value_enum, default_value_t = ExampleCodeType::CodeCapacityPlanarCode)]
52 pub code_type: ExampleCodeType,
53 #[clap(long, default_value_t = ("{}").to_string())]
55 pub code_config: String,
56 #[clap(long, action)]
58 pub enable_visualizer: bool,
59 #[clap(long, default_value_t = crate::visualize::static_visualize_data_filename())]
61 pub visualizer_filename: String,
62 #[clap(long, action)]
64 pub print_syndrome_pattern: bool,
65 #[clap(long, value_enum, default_value_t = Verifier::BlossomV)]
67 pub verifier: Verifier,
68 #[clap(short = 'r', long, default_value_t = 1000)]
70 pub total_rounds: usize,
71 #[clap(short = 'p', long, value_enum, default_value_t = PrimalDualType::Serial)]
73 pub primal_dual_type: PrimalDualType,
74 #[clap(long, default_value_t = ("{}").to_string())]
76 pub primal_dual_config: String,
77 #[clap(long, value_enum, default_value_t = PartitionStrategy::None)]
79 pub partition_strategy: PartitionStrategy,
80 #[clap(long, default_value_t = ("{}").to_string())]
82 pub partition_config: String,
83 #[clap(long, default_value_t = format!(""))]
85 pub pb_message: String,
86 #[clap(long, action)]
88 pub use_deterministic_seed: bool,
89 #[clap(long)]
91 pub benchmark_profiler_output: Option<String>,
92 #[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(BenchmarkParameters),
103 #[cfg(feature = "qecp_integrate")]
104 Qecp(qecp::cli::BenchmarkParameters),
105 Test {
107 #[clap(subcommand)]
108 command: TestCommands,
109 },
110 VisualizeSyndromes(VisualizeSyndromesParameters),
112}
113
114#[derive(Parser, Clone, Debug)]
115pub struct VisualizeSyndromesParameters {
116 #[clap(value_parser)]
118 pub filepath: String,
119 #[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 Serial {
128 #[clap(short = 'c', long, action)]
130 print_command: bool,
131 #[clap(short = 'v', long, action)]
133 enable_visualizer: bool,
134 #[clap(short = 'd', long, action)]
136 disable_blossom: bool,
137 #[clap(short = 's', long, action)]
139 print_syndrome_pattern: bool,
140 },
141 DualParallel {
143 #[clap(short = 'c', long, action)]
145 print_command: bool,
146 #[clap(short = 'v', long, action)]
148 enable_visualizer: bool,
149 #[clap(short = 'd', long, action)]
151 disable_blossom: bool,
152 #[clap(short = 's', long, action)]
154 print_syndrome_pattern: bool,
155 },
156 Parallel {
158 #[clap(short = 'c', long, action)]
160 print_command: bool,
161 #[clap(short = 'v', long, action)]
163 enable_visualizer: bool,
164 #[clap(short = 'd', long, action)]
166 disable_blossom: bool,
167 #[clap(short = 's', long, action)]
169 print_syndrome_pattern: bool,
170 },
171}
172
173#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Debug)]
176#[serde(rename_all = "kebab-case")]
177pub enum ExampleCodeType {
178 CodeCapacityRepetitionCode,
180 PhenomenologicalRepetitionCode,
182 CircuitLevelRepetitionCode,
184 CodeCapacityPlanarCode,
186 PhenomenologicalPlanarCode,
188 PhenomenologicalPlanarCodeParallel,
190 CircuitLevelPlanarCode,
192 CircuitLevelPlanarCodeParallel,
194 ErrorPatternReader,
196 CodeCapacityRotatedCode,
198 PhenomenologicalRotatedCode,
200 #[serde(rename = "qec-playground-code")]
202 QECPlaygroundCode,
203}
204
205#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Debug)]
206pub enum PartitionStrategy {
207 None,
209 CodeCapacityPlanarCodeVerticalPartitionHalf,
211 CodeCapacityPlanarCodeVerticalPartitionFour,
213 CodeCapacityRepetitionCodePartitionHalf,
215 PhenomenologicalPlanarCodeTimePartition,
217 PhenomenologicalRotatedCodeTimePartition,
219}
220
221#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Debug)]
222pub enum PrimalDualType {
223 Serial,
225 DualParallel,
227 Parallel,
229 ErrorPatternLogger,
231 BlossomV,
233}
234
235#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Debug)]
236pub enum Verifier {
237 None,
239 BlossomV,
241 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 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_visualize_link(visualizer_filename.clone());
287 }
288 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 let disable_progress_bar = env::var("DISABLE_PROGRESS_BAR").is_ok();
326 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 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(); 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 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 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 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 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 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 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 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 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 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 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 assert_eq!(
1067 primal_dual_solver.sum_dual_variables(),
1068 blossom_total_weight,
1069 "unexpected final dual variable sum"
1070 );
1071 let (fusion_mwpm, fusion_total_weight) =
1073 get_primal_dual_solver_total_weight(primal_dual_solver, syndrome_pattern, &self.initializer);
1074 assert_eq!(
1076 fusion_total_weight, blossom_total_weight,
1077 "unexpected final dual variable sum"
1078 );
1079 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 self.subgraph_builder.clear();
1087 self.subgraph_builder.load_erasures(&syndrome_pattern.erasures);
1088 self.subgraph_builder.load_perfect_matching(&fusion_mwpm);
1089 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}