1use scirs2_core::ndarray::Array2;
18use serde::{Deserialize, Serialize};
19use std::collections::{HashMap, HashSet, VecDeque};
20
21use crate::circuit_interfaces::{
22 CircuitInterface, InterfaceCircuit, InterfaceGate, InterfaceGateType,
23};
24use crate::error::Result;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum LDPCConstructionMethod {
29 RandomRegular,
31 ProgressiveEdgeGrowth,
33 Gallager,
35 MacKay,
37 QuantumBicycle,
39 SurfaceCode,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum BeliefPropagationAlgorithm {
46 SumProduct,
48 MinSum,
50 NormalizedMinSum,
52 OffsetMinSum,
54 LayeredBP,
56}
57
58#[derive(Debug, Clone)]
60pub struct LDPCConfig {
61 pub k: usize,
63 pub n: usize,
65 pub m: usize,
67 pub dv: usize,
69 pub dc: usize,
71 pub construction_method: LDPCConstructionMethod,
73 pub bp_algorithm: BeliefPropagationAlgorithm,
75 pub max_bp_iterations: usize,
77 pub convergence_threshold: f64,
79 pub damping_factor: f64,
81 pub early_termination: bool,
83 pub noise_variance: f64,
85}
86
87impl Default for LDPCConfig {
88 fn default() -> Self {
89 Self {
90 k: 10,
91 n: 20,
92 m: 10,
93 dv: 3,
94 dc: 6,
95 construction_method: LDPCConstructionMethod::ProgressiveEdgeGrowth,
96 bp_algorithm: BeliefPropagationAlgorithm::SumProduct,
97 max_bp_iterations: 50,
98 convergence_threshold: 1e-6,
99 damping_factor: 0.8,
100 early_termination: true,
101 noise_variance: 0.1,
102 }
103 }
104}
105
106#[derive(Debug, Clone)]
108pub struct TannerGraph {
109 pub variable_nodes: Vec<VariableNode>,
111 pub check_nodes: Vec<CheckNode>,
113 pub adjacency_matrix: Array2<bool>,
115 pub parity_check_matrix: Array2<u8>,
117}
118
119#[derive(Debug, Clone)]
121pub struct VariableNode {
122 pub id: usize,
124 pub connected_checks: Vec<usize>,
126 pub belief: f64,
128 pub channel_llr: f64,
130 pub incoming_messages: HashMap<usize, f64>,
132}
133
134#[derive(Debug, Clone)]
136pub struct CheckNode {
137 pub id: usize,
139 pub connected_variables: Vec<usize>,
141 pub incoming_messages: HashMap<usize, f64>,
143 pub syndrome: bool,
145}
146
147impl VariableNode {
148 pub fn new(id: usize) -> Self {
150 Self {
151 id,
152 connected_checks: Vec::new(),
153 belief: 0.0,
154 channel_llr: 0.0,
155 incoming_messages: HashMap::new(),
156 }
157 }
158
159 pub fn update_belief(&mut self) {
161 self.belief = self.channel_llr + self.incoming_messages.values().sum::<f64>();
162 }
163
164 pub fn compute_outgoing_message(&self, check_id: usize) -> f64 {
166 let message_sum: f64 = self
167 .incoming_messages
168 .iter()
169 .filter(|(&id, _)| id != check_id)
170 .map(|(_, &value)| value)
171 .sum();
172 self.channel_llr + message_sum
173 }
174}
175
176impl CheckNode {
177 pub fn new(id: usize) -> Self {
179 Self {
180 id,
181 connected_variables: Vec::new(),
182 incoming_messages: HashMap::new(),
183 syndrome: false,
184 }
185 }
186
187 pub fn compute_outgoing_message_sum_product(&self, var_id: usize) -> f64 {
189 let product: f64 = self
190 .incoming_messages
191 .iter()
192 .filter(|(&id, _)| id != var_id)
193 .map(|(_, &msg)| msg.tanh() / 2.0)
194 .product();
195
196 2.0 * product.atanh()
197 }
198
199 pub fn compute_outgoing_message_min_sum(&self, var_id: usize) -> f64 {
201 let other_messages: Vec<f64> = self
202 .incoming_messages
203 .iter()
204 .filter(|(&id, _)| id != var_id)
205 .map(|(_, &msg)| msg)
206 .collect();
207
208 if other_messages.is_empty() {
209 return 0.0;
210 }
211
212 let sign_product: f64 = other_messages
213 .iter()
214 .map(|&msg| if msg >= 0.0 { 1.0 } else { -1.0 })
215 .product();
216
217 let min_magnitude = other_messages
218 .iter()
219 .map(|&msg| msg.abs())
220 .fold(f64::INFINITY, |a, b| a.min(b));
221
222 sign_product * min_magnitude
223 }
224}
225
226pub struct QuantumLDPCCode {
228 config: LDPCConfig,
230 tanner_graph: TannerGraph,
232 #[allow(dead_code)]
234 x_stabilizers: Array2<u8>,
235 #[allow(dead_code)]
237 z_stabilizers: Array2<u8>,
238 #[allow(dead_code)]
240 logical_x_ops: Array2<u8>,
241 #[allow(dead_code)]
243 logical_z_ops: Array2<u8>,
244 #[allow(dead_code)]
246 circuit_interface: CircuitInterface,
247 stats: LDPCStats,
249}
250
251#[derive(Debug, Clone, Default, Serialize, Deserialize)]
253pub struct LDPCStats {
254 pub total_decodings: usize,
256 pub successful_decodings: usize,
258 pub avg_bp_iterations: f64,
260 pub block_error_rate: f64,
262 pub bit_error_rate: f64,
264 pub avg_decoding_time_ms: f64,
266 pub threshold_estimate: f64,
268 pub convergence_rate: f64,
270}
271
272impl LDPCStats {
273 pub fn update_after_decoding(&mut self, success: bool, iterations: usize, time_ms: f64) {
275 self.total_decodings += 1;
276 if success {
277 self.successful_decodings += 1;
278 }
279
280 let prev_avg_iter = self.avg_bp_iterations;
281 self.avg_bp_iterations = prev_avg_iter
282 .mul_add((self.total_decodings - 1) as f64, iterations as f64)
283 / self.total_decodings as f64;
284
285 let prev_avg_time = self.avg_decoding_time_ms;
286 self.avg_decoding_time_ms = prev_avg_time
287 .mul_add((self.total_decodings - 1) as f64, time_ms)
288 / self.total_decodings as f64;
289
290 self.block_error_rate =
291 1.0 - (self.successful_decodings as f64 / self.total_decodings as f64);
292 self.convergence_rate = self.successful_decodings as f64 / self.total_decodings as f64;
293 }
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
298pub struct BPDecodingResult {
299 pub decoded_bits: Vec<bool>,
301 pub final_syndrome: Vec<bool>,
303 pub iterations: usize,
305 pub converged: bool,
307 pub final_llrs: Vec<f64>,
309 pub decoding_time_ms: f64,
311 pub success: bool,
313}
314
315impl QuantumLDPCCode {
316 pub fn new(config: LDPCConfig) -> Result<Self> {
318 let circuit_interface = CircuitInterface::new(Default::default())?;
319
320 let tanner_graph = Self::construct_tanner_graph(&config)?;
322
323 let (x_stabilizers, z_stabilizers) =
325 Self::generate_stabilizer_matrices(&config, &tanner_graph)?;
326
327 let (logical_x_ops, logical_z_ops) =
329 Self::generate_logical_operators(&config, &x_stabilizers, &z_stabilizers)?;
330
331 Ok(Self {
332 config,
333 tanner_graph,
334 x_stabilizers,
335 z_stabilizers,
336 logical_x_ops,
337 logical_z_ops,
338 circuit_interface,
339 stats: LDPCStats::default(),
340 })
341 }
342
343 fn construct_tanner_graph(config: &LDPCConfig) -> Result<TannerGraph> {
345 match config.construction_method {
346 LDPCConstructionMethod::RandomRegular => Self::construct_random_regular(config),
347 LDPCConstructionMethod::ProgressiveEdgeGrowth => Self::construct_peg(config),
348 LDPCConstructionMethod::Gallager => Self::construct_gallager(config),
349 LDPCConstructionMethod::MacKay => Self::construct_mackay(config),
350 LDPCConstructionMethod::QuantumBicycle => Self::construct_quantum_bicycle(config),
351 LDPCConstructionMethod::SurfaceCode => Self::construct_surface_code(config),
352 }
353 }
354
355 fn construct_random_regular(config: &LDPCConfig) -> Result<TannerGraph> {
357 let mut variable_nodes = Vec::with_capacity(config.n);
358 let mut check_nodes = Vec::with_capacity(config.m);
359 let mut adjacency_matrix = Array2::from_elem((config.n, config.m), false);
360
361 for i in 0..config.n {
363 variable_nodes.push(VariableNode::new(i));
364 }
365 for i in 0..config.m {
366 check_nodes.push(CheckNode::new(i));
367 }
368
369 let mut edges = Vec::new();
371
372 for var_id in 0..config.n {
374 for _ in 0..config.dv {
375 edges.push(var_id);
376 }
377 }
378
379 for i in 0..edges.len() {
381 let j = fastrand::usize(i..edges.len());
382 edges.swap(i, j);
383 }
384
385 let mut edge_idx = 0;
387 for check_id in 0..config.m {
388 for _ in 0..config.dc {
389 if edge_idx < edges.len() {
390 let var_id = edges[edge_idx];
391
392 variable_nodes[var_id].connected_checks.push(check_id);
394 check_nodes[check_id].connected_variables.push(var_id);
395 adjacency_matrix[[var_id, check_id]] = true;
396
397 edge_idx += 1;
398 }
399 }
400 }
401
402 let mut parity_check_matrix = Array2::zeros((config.m, config.n));
404 for (var_id, check_id) in adjacency_matrix.indexed_iter() {
405 if *check_id {
406 parity_check_matrix[[var_id.1, var_id.0]] = 1;
407 }
408 }
409
410 Ok(TannerGraph {
411 variable_nodes,
412 check_nodes,
413 adjacency_matrix,
414 parity_check_matrix,
415 })
416 }
417
418 fn construct_peg(config: &LDPCConfig) -> Result<TannerGraph> {
420 let mut variable_nodes = Vec::with_capacity(config.n);
421 let mut check_nodes = Vec::with_capacity(config.m);
422 let mut adjacency_matrix = Array2::from_elem((config.n, config.m), false);
423
424 for i in 0..config.n {
426 variable_nodes.push(VariableNode::new(i));
427 }
428 for i in 0..config.m {
429 check_nodes.push(CheckNode::new(i));
430 }
431
432 for var_id in 0..config.n {
434 let mut connected_checks = HashSet::new();
435
436 for _ in 0..config.dv {
437 let best_check = Self::find_best_check_for_peg(
438 var_id,
439 &connected_checks,
440 &variable_nodes,
441 &check_nodes,
442 &adjacency_matrix,
443 config.m,
444 );
445
446 if let Some(check_id) = best_check {
447 variable_nodes[var_id].connected_checks.push(check_id);
449 check_nodes[check_id].connected_variables.push(var_id);
450 adjacency_matrix[[var_id, check_id]] = true;
451 connected_checks.insert(check_id);
452 }
453 }
454 }
455
456 let mut parity_check_matrix = Array2::zeros((config.m, config.n));
458 for (var_id, check_id) in adjacency_matrix.indexed_iter() {
459 if *check_id {
460 parity_check_matrix[[var_id.1, var_id.0]] = 1;
461 }
462 }
463
464 Ok(TannerGraph {
465 variable_nodes,
466 check_nodes,
467 adjacency_matrix,
468 parity_check_matrix,
469 })
470 }
471
472 fn find_best_check_for_peg(
474 var_id: usize,
475 connected_checks: &HashSet<usize>,
476 _variable_nodes: &[VariableNode],
477 _check_nodes: &[CheckNode],
478 adjacency_matrix: &Array2<bool>,
479 num_checks: usize,
480 ) -> Option<usize> {
481 let mut best_check = None;
482 let mut best_girth = 0;
483
484 for check_id in 0..num_checks {
485 if connected_checks.contains(&check_id) {
486 continue;
487 }
488
489 let girth = Self::calculate_local_girth(var_id, check_id, adjacency_matrix);
491
492 if girth > best_girth {
493 best_girth = girth;
494 best_check = Some(check_id);
495 }
496 }
497
498 best_check
499 }
500
501 fn calculate_local_girth(
503 var_id: usize,
504 check_id: usize,
505 adjacency_matrix: &Array2<bool>,
506 ) -> usize {
507 let mut visited_vars = HashSet::new();
509 let mut visited_checks = HashSet::new();
510 let mut queue = VecDeque::new();
511
512 queue.push_back((var_id, 0, true)); visited_vars.insert(var_id);
514
515 while let Some((node_id, dist, is_var)) = queue.pop_front() {
516 if dist > 6 {
517 break;
519 }
520
521 if is_var {
522 for (check_idx, &connected) in adjacency_matrix.row(node_id).indexed_iter() {
524 if connected && check_idx != check_id {
525 if visited_checks.contains(&check_idx) {
526 return dist * 2; }
528 visited_checks.insert(check_idx);
529 queue.push_back((check_idx, dist + 1, false));
530 }
531 }
532 } else {
533 for (var_idx, &connected) in adjacency_matrix.column(node_id).indexed_iter() {
535 if connected && var_idx != var_id {
536 if visited_vars.contains(&var_idx) {
537 return dist * 2 + 1; }
539 visited_vars.insert(var_idx);
540 queue.push_back((var_idx, dist + 1, true));
541 }
542 }
543 }
544 }
545
546 12 }
548
549 fn construct_gallager(config: &LDPCConfig) -> Result<TannerGraph> {
551 Self::construct_random_regular(config)
554 }
555
556 fn construct_mackay(config: &LDPCConfig) -> Result<TannerGraph> {
558 Self::construct_peg(config)
561 }
562
563 fn construct_quantum_bicycle(config: &LDPCConfig) -> Result<TannerGraph> {
565 let mut variable_nodes = Vec::with_capacity(config.n);
567 let mut check_nodes = Vec::with_capacity(config.m);
568 let mut adjacency_matrix = Array2::from_elem((config.n, config.m), false);
569
570 for i in 0..config.n {
572 variable_nodes.push(VariableNode::new(i));
573 }
574 for i in 0..config.m {
575 check_nodes.push(CheckNode::new(i));
576 }
577
578 let l = config.n / 2;
580 for i in 0..l {
581 for j in 0..config.dv {
582 let check_id = (i + j * l / config.dv) % config.m;
583
584 variable_nodes[i].connected_checks.push(check_id);
586 check_nodes[check_id].connected_variables.push(i);
587 adjacency_matrix[[i, check_id]] = true;
588
589 if i + l < config.n {
591 variable_nodes[i + l].connected_checks.push(check_id);
592 check_nodes[check_id].connected_variables.push(i + l);
593 adjacency_matrix[[i + l, check_id]] = true;
594 }
595 }
596 }
597
598 let mut parity_check_matrix = Array2::zeros((config.m, config.n));
600 for (var_id, check_id) in adjacency_matrix.indexed_iter() {
601 if *check_id {
602 parity_check_matrix[[var_id.1, var_id.0]] = 1;
603 }
604 }
605
606 Ok(TannerGraph {
607 variable_nodes,
608 check_nodes,
609 adjacency_matrix,
610 parity_check_matrix,
611 })
612 }
613
614 fn construct_surface_code(config: &LDPCConfig) -> Result<TannerGraph> {
616 let d = (config.n as f64).sqrt() as usize; let mut variable_nodes = Vec::with_capacity(config.n);
619 let mut check_nodes = Vec::with_capacity(config.m);
620 let mut adjacency_matrix = Array2::from_elem((config.n, config.m), false);
621
622 for i in 0..config.n {
624 variable_nodes.push(VariableNode::new(i));
625 }
626 for i in 0..config.m {
627 check_nodes.push(CheckNode::new(i));
628 }
629
630 for check_id in 0..config.m {
632 let row = check_id / d;
633 let col = check_id % d;
634
635 let neighbors = [
637 (row.wrapping_sub(1), col),
638 (row + 1, col),
639 (row, col.wrapping_sub(1)),
640 (row, col + 1),
641 ];
642
643 for (r, c) in &neighbors {
644 if *r < d && *c < d {
645 let var_id = r * d + c;
646 if var_id < config.n {
647 variable_nodes[var_id].connected_checks.push(check_id);
648 check_nodes[check_id].connected_variables.push(var_id);
649 adjacency_matrix[[var_id, check_id]] = true;
650 }
651 }
652 }
653 }
654
655 let mut parity_check_matrix = Array2::zeros((config.m, config.n));
657 for (var_id, check_id) in adjacency_matrix.indexed_iter() {
658 if *check_id {
659 parity_check_matrix[[var_id.1, var_id.0]] = 1;
660 }
661 }
662
663 Ok(TannerGraph {
664 variable_nodes,
665 check_nodes,
666 adjacency_matrix,
667 parity_check_matrix,
668 })
669 }
670
671 fn generate_stabilizer_matrices(
673 _config: &LDPCConfig,
674 tanner_graph: &TannerGraph,
675 ) -> Result<(Array2<u8>, Array2<u8>)> {
676 let x_stabilizers = tanner_graph.parity_check_matrix.clone();
678 let z_stabilizers = tanner_graph.parity_check_matrix.clone();
679
680 Ok((x_stabilizers, z_stabilizers))
681 }
682
683 fn generate_logical_operators(
685 config: &LDPCConfig,
686 _x_stabilizers: &Array2<u8>,
687 _z_stabilizers: &Array2<u8>,
688 ) -> Result<(Array2<u8>, Array2<u8>)> {
689 let k = config.k;
690 let n = config.n;
691
692 let mut logical_x_ops = Array2::zeros((k, n));
694 let mut logical_z_ops = Array2::zeros((k, n));
695
696 for i in 0..k.min(n) {
698 logical_x_ops[[i, i]] = 1;
699 logical_z_ops[[i, i]] = 1;
700 }
701
702 Ok((logical_x_ops, logical_z_ops))
703 }
704
705 pub fn decode_belief_propagation(
707 &mut self,
708 received_llrs: &[f64],
709 syndrome: &[bool],
710 ) -> Result<BPDecodingResult> {
711 let start_time = std::time::Instant::now();
712
713 for (i, &llr) in received_llrs.iter().enumerate() {
715 if i < self.tanner_graph.variable_nodes.len() {
716 self.tanner_graph.variable_nodes[i].channel_llr = llr;
717 }
718 }
719
720 for (i, &syn) in syndrome.iter().enumerate() {
722 if i < self.tanner_graph.check_nodes.len() {
723 self.tanner_graph.check_nodes[i].syndrome = syn;
724 }
725 }
726
727 let mut converged = false;
728 let mut iteration = 0;
729
730 while iteration < self.config.max_bp_iterations && !converged {
732 self.update_variable_to_check_messages();
734
735 self.update_check_to_variable_messages();
737
738 self.update_variable_beliefs();
740
741 converged = self.check_convergence();
743
744 if self.config.early_termination && converged {
745 break;
746 }
747
748 iteration += 1;
749 }
750
751 let decoded_bits = self.extract_decoded_bits();
753
754 let final_syndrome = self.calculate_syndrome(&decoded_bits);
756
757 let success = final_syndrome.iter().all(|&s| !s);
759
760 let decoding_time_ms = start_time.elapsed().as_secs_f64() * 1000.0;
761
762 self.stats
764 .update_after_decoding(success, iteration, decoding_time_ms);
765
766 let final_llrs: Vec<f64> = self
768 .tanner_graph
769 .variable_nodes
770 .iter()
771 .map(|node| node.belief)
772 .collect();
773
774 Ok(BPDecodingResult {
775 decoded_bits,
776 final_syndrome,
777 iterations: iteration,
778 converged,
779 final_llrs,
780 decoding_time_ms,
781 success,
782 })
783 }
784
785 fn update_variable_to_check_messages(&mut self) {
787 for var_node in &mut self.tanner_graph.variable_nodes {
788 for &check_id in &var_node.connected_checks.clone() {
789 let message = var_node.compute_outgoing_message(check_id);
790
791 let old_message = self.tanner_graph.check_nodes[check_id]
793 .incoming_messages
794 .get(&var_node.id)
795 .unwrap_or(&0.0);
796
797 let damped_message = self
798 .config
799 .damping_factor
800 .mul_add(message, (1.0 - self.config.damping_factor) * old_message);
801
802 self.tanner_graph.check_nodes[check_id]
803 .incoming_messages
804 .insert(var_node.id, damped_message);
805 }
806 }
807 }
808
809 fn update_check_to_variable_messages(&mut self) {
811 for check_node in &mut self.tanner_graph.check_nodes {
812 for &var_id in &check_node.connected_variables.clone() {
813 let message = match self.config.bp_algorithm {
814 BeliefPropagationAlgorithm::SumProduct => {
815 check_node.compute_outgoing_message_sum_product(var_id)
816 }
817 BeliefPropagationAlgorithm::MinSum => {
818 check_node.compute_outgoing_message_min_sum(var_id)
819 }
820 BeliefPropagationAlgorithm::NormalizedMinSum => {
821 let min_sum_msg = check_node.compute_outgoing_message_min_sum(var_id);
822 min_sum_msg * 0.75 }
824 BeliefPropagationAlgorithm::OffsetMinSum => {
825 let min_sum_msg = check_node.compute_outgoing_message_min_sum(var_id);
826 let offset = 0.1;
827 if min_sum_msg.abs() > offset {
828 min_sum_msg.signum() * (min_sum_msg.abs() - offset)
829 } else {
830 0.0
831 }
832 }
833 BeliefPropagationAlgorithm::LayeredBP => {
834 check_node.compute_outgoing_message_sum_product(var_id)
836 }
837 };
838
839 let old_message = self.tanner_graph.variable_nodes[var_id]
841 .incoming_messages
842 .get(&check_node.id)
843 .unwrap_or(&0.0);
844
845 let damped_message = self
846 .config
847 .damping_factor
848 .mul_add(message, (1.0 - self.config.damping_factor) * old_message);
849
850 self.tanner_graph.variable_nodes[var_id]
851 .incoming_messages
852 .insert(check_node.id, damped_message);
853 }
854 }
855 }
856
857 fn update_variable_beliefs(&mut self) {
859 for var_node in &mut self.tanner_graph.variable_nodes {
860 var_node.update_belief();
861 }
862 }
863
864 fn check_convergence(&self) -> bool {
866 let decoded_bits = self.extract_decoded_bits();
868 let syndrome = self.calculate_syndrome(&decoded_bits);
869
870 syndrome.iter().all(|&s| !s)
871 }
872
873 fn extract_decoded_bits(&self) -> Vec<bool> {
875 self.tanner_graph
876 .variable_nodes
877 .iter()
878 .map(|node| node.belief < 0.0)
879 .collect()
880 }
881
882 fn calculate_syndrome(&self, codeword: &[bool]) -> Vec<bool> {
884 let mut syndrome = vec![false; self.tanner_graph.check_nodes.len()];
885
886 for (check_id, check_node) in self.tanner_graph.check_nodes.iter().enumerate() {
887 let mut parity = false;
888 for &var_id in &check_node.connected_variables {
889 if var_id < codeword.len() && codeword[var_id] {
890 parity = !parity;
891 }
892 }
893 syndrome[check_id] = parity;
894 }
895
896 syndrome
897 }
898
899 pub fn syndrome_circuit(&self) -> Result<InterfaceCircuit> {
901 let num_data_qubits = self.config.n;
902 let num_syndrome_qubits = self.config.m;
903 let total_qubits = num_data_qubits + num_syndrome_qubits;
904
905 let mut circuit = InterfaceCircuit::new(total_qubits, num_syndrome_qubits);
906
907 for (check_id, check_node) in self.tanner_graph.check_nodes.iter().enumerate() {
909 let syndrome_qubit = num_data_qubits + check_id;
910
911 for &var_id in &check_node.connected_variables {
912 if var_id < num_data_qubits {
913 circuit.add_gate(InterfaceGate::new(
914 InterfaceGateType::CNOT,
915 vec![var_id, syndrome_qubit],
916 ));
917 }
918 }
919 }
920
921 Ok(circuit)
922 }
923
924 pub fn estimate_threshold(
926 &mut self,
927 noise_range: (f64, f64),
928 num_trials: usize,
929 ) -> Result<f64> {
930 let (min_noise, max_noise) = noise_range;
931 let mut threshold = f64::midpoint(min_noise, max_noise);
932 let mut search_range = max_noise - min_noise;
933
934 while search_range > 0.001 {
936 self.config.noise_variance = threshold;
937
938 let mut successes = 0;
939 for _ in 0..num_trials {
940 let errors: Vec<bool> = (0..self.config.n)
942 .map(|_| fastrand::f64() < threshold)
943 .collect();
944
945 let llrs: Vec<f64> = errors
947 .iter()
948 .map(|&error| {
949 if error {
950 -2.0 / self.config.noise_variance
951 } else {
952 2.0 / self.config.noise_variance
953 }
954 })
955 .collect();
956
957 let syndrome = self.calculate_syndrome(&errors);
959
960 if let Ok(result) = self.decode_belief_propagation(&llrs, &syndrome) {
962 if result.success {
963 successes += 1;
964 }
965 }
966 }
967
968 let success_rate = successes as f64 / num_trials as f64;
969
970 if success_rate > 0.5 {
971 threshold = f64::midpoint(threshold, max_noise);
972 } else {
973 threshold = f64::midpoint(min_noise, threshold);
974 }
975
976 search_range /= 2.0;
977 }
978
979 self.stats.threshold_estimate = threshold;
980 Ok(threshold)
981 }
982
983 pub const fn get_stats(&self) -> &LDPCStats {
985 &self.stats
986 }
987
988 pub fn reset_stats(&mut self) {
990 self.stats = LDPCStats::default();
991 }
992
993 pub const fn get_parameters(&self) -> (usize, usize, usize) {
995 (self.config.n, self.config.k, self.config.m)
996 }
997
998 pub const fn get_tanner_graph(&self) -> &TannerGraph {
1000 &self.tanner_graph
1001 }
1002}
1003
1004pub fn benchmark_quantum_ldpc_codes() -> Result<HashMap<String, f64>> {
1006 let mut results = HashMap::new();
1007
1008 let configs = vec![
1010 LDPCConfig {
1011 k: 10,
1012 n: 20,
1013 m: 10,
1014 construction_method: LDPCConstructionMethod::RandomRegular,
1015 bp_algorithm: BeliefPropagationAlgorithm::SumProduct,
1016 ..Default::default()
1017 },
1018 LDPCConfig {
1019 k: 15,
1020 n: 30,
1021 m: 15,
1022 construction_method: LDPCConstructionMethod::ProgressiveEdgeGrowth,
1023 bp_algorithm: BeliefPropagationAlgorithm::MinSum,
1024 ..Default::default()
1025 },
1026 LDPCConfig {
1027 k: 20,
1028 n: 40,
1029 m: 20,
1030 construction_method: LDPCConstructionMethod::QuantumBicycle,
1031 bp_algorithm: BeliefPropagationAlgorithm::NormalizedMinSum,
1032 ..Default::default()
1033 },
1034 ];
1035
1036 for (i, config) in configs.into_iter().enumerate() {
1037 let start = std::time::Instant::now();
1038
1039 let mut ldpc_code = QuantumLDPCCode::new(config)?;
1040
1041 for _ in 0..50 {
1043 let errors: Vec<bool> = (0..ldpc_code.config.n)
1045 .map(|_| fastrand::f64() < 0.05)
1046 .collect();
1047
1048 let llrs: Vec<f64> = errors
1050 .iter()
1051 .map(|&error| if error { -1.0 } else { 1.0 })
1052 .collect();
1053
1054 let syndrome = ldpc_code.calculate_syndrome(&errors);
1056
1057 let _result = ldpc_code.decode_belief_propagation(&llrs, &syndrome)?;
1059 }
1060
1061 let time = start.elapsed().as_secs_f64() * 1000.0;
1062 results.insert(format!("config_{i}"), time);
1063
1064 let stats = ldpc_code.get_stats();
1066 results.insert(format!("config_{i}_success_rate"), stats.convergence_rate);
1067 results.insert(
1068 format!("config_{i}_avg_iterations"),
1069 stats.avg_bp_iterations,
1070 );
1071 }
1072
1073 Ok(results)
1074}
1075
1076#[cfg(test)]
1077mod tests {
1078 use super::*;
1079 use approx::assert_abs_diff_eq;
1080
1081 #[test]
1082 fn test_ldpc_code_creation() {
1083 let config = LDPCConfig::default();
1084 let ldpc_code = QuantumLDPCCode::new(config);
1085 assert!(ldpc_code.is_ok());
1086 }
1087
1088 #[test]
1089 fn test_tanner_graph_construction() {
1090 let config = LDPCConfig {
1091 k: 5,
1092 n: 10,
1093 m: 5,
1094 dv: 2,
1095 dc: 4,
1096 ..Default::default()
1097 };
1098
1099 let tanner_graph = QuantumLDPCCode::construct_random_regular(&config);
1100 assert!(tanner_graph.is_ok());
1101
1102 let graph = tanner_graph.unwrap();
1103 assert_eq!(graph.variable_nodes.len(), 10);
1104 assert_eq!(graph.check_nodes.len(), 5);
1105 }
1106
1107 #[test]
1108 fn test_variable_node_operations() {
1109 let mut var_node = VariableNode::new(0);
1110 var_node.channel_llr = 1.0;
1111 var_node.incoming_messages.insert(1, 0.5);
1112 var_node.incoming_messages.insert(2, -0.3);
1113
1114 var_node.update_belief();
1115 assert_abs_diff_eq!(var_node.belief, 1.2, epsilon = 1e-10);
1116
1117 let outgoing = var_node.compute_outgoing_message(1);
1118 assert_abs_diff_eq!(outgoing, 0.7, epsilon = 1e-10);
1119 }
1120
1121 #[test]
1122 fn test_check_node_operations() {
1123 let mut check_node = CheckNode::new(0);
1124 check_node.incoming_messages.insert(1, 0.8);
1125 check_node.incoming_messages.insert(2, -0.6);
1126 check_node.incoming_messages.insert(3, 1.2);
1127
1128 let sum_product_msg = check_node.compute_outgoing_message_sum_product(1);
1129 assert!(sum_product_msg.is_finite());
1130
1131 let min_sum_msg = check_node.compute_outgoing_message_min_sum(1);
1132 assert_abs_diff_eq!(min_sum_msg.abs(), 0.6, epsilon = 1e-10);
1133 }
1134
1135 #[test]
1136 fn test_belief_propagation_decoding() {
1137 let config = LDPCConfig {
1138 k: 3,
1139 n: 6,
1140 m: 3,
1141 max_bp_iterations: 10,
1142 ..Default::default()
1143 };
1144
1145 let mut ldpc_code = QuantumLDPCCode::new(config).unwrap();
1146
1147 let llrs = vec![1.0, -1.0, 1.0, 1.0, -1.0, 1.0];
1148 let syndrome = vec![false, true, false];
1149
1150 let result = ldpc_code.decode_belief_propagation(&llrs, &syndrome);
1151 assert!(result.is_ok());
1152
1153 let bp_result = result.unwrap();
1154 assert_eq!(bp_result.decoded_bits.len(), 6);
1155 assert!(bp_result.iterations <= 10);
1156 }
1157
1158 #[test]
1159 fn test_syndrome_calculation() {
1160 let config = LDPCConfig {
1161 k: 2,
1162 n: 4,
1163 m: 2,
1164 ..Default::default()
1165 };
1166
1167 let ldpc_code = QuantumLDPCCode::new(config).unwrap();
1168
1169 let codeword = vec![false, true, false, true];
1170 let syndrome = ldpc_code.calculate_syndrome(&codeword);
1171
1172 assert_eq!(syndrome.len(), 2);
1173 }
1174
1175 #[test]
1176 fn test_syndrome_circuit_generation() {
1177 let config = LDPCConfig {
1178 k: 3,
1179 n: 6,
1180 m: 3,
1181 ..Default::default()
1182 };
1183
1184 let ldpc_code = QuantumLDPCCode::new(config).unwrap();
1185 let circuit = ldpc_code.syndrome_circuit();
1186
1187 assert!(circuit.is_ok());
1188 let syndrome_circuit = circuit.unwrap();
1189 assert_eq!(syndrome_circuit.num_qubits, 9); }
1191
1192 #[test]
1193 fn test_different_construction_methods() {
1194 let base_config = LDPCConfig {
1195 k: 3,
1196 n: 6,
1197 m: 3,
1198 ..Default::default()
1199 };
1200
1201 let methods = vec![
1202 LDPCConstructionMethod::RandomRegular,
1203 LDPCConstructionMethod::ProgressiveEdgeGrowth,
1204 LDPCConstructionMethod::QuantumBicycle,
1205 LDPCConstructionMethod::SurfaceCode,
1206 ];
1207
1208 for method in methods {
1209 let mut config = base_config.clone();
1210 config.construction_method = method;
1211
1212 let ldpc_code = QuantumLDPCCode::new(config);
1213 assert!(ldpc_code.is_ok(), "Failed for method: {method:?}");
1214 }
1215 }
1216
1217 #[test]
1218 fn test_different_bp_algorithms() {
1219 let base_config = LDPCConfig {
1220 k: 3,
1221 n: 6,
1222 m: 3,
1223 ..Default::default()
1224 };
1225
1226 let algorithms = vec![
1227 BeliefPropagationAlgorithm::SumProduct,
1228 BeliefPropagationAlgorithm::MinSum,
1229 BeliefPropagationAlgorithm::NormalizedMinSum,
1230 BeliefPropagationAlgorithm::OffsetMinSum,
1231 ];
1232
1233 for algorithm in algorithms {
1234 let mut config = base_config.clone();
1235 config.bp_algorithm = algorithm;
1236
1237 let mut ldpc_code = QuantumLDPCCode::new(config).unwrap();
1238
1239 let llrs = vec![1.0, -1.0, 1.0, 1.0, -1.0, 1.0];
1240 let syndrome = vec![false, true, false];
1241
1242 let result = ldpc_code.decode_belief_propagation(&llrs, &syndrome);
1243 assert!(result.is_ok(), "Failed for algorithm: {algorithm:?}");
1244 }
1245 }
1246
1247 #[test]
1248 fn test_stats_updates() {
1249 let mut stats = LDPCStats::default();
1250
1251 stats.update_after_decoding(true, 5, 10.0);
1252 assert_eq!(stats.total_decodings, 1);
1253 assert_eq!(stats.successful_decodings, 1);
1254 assert_abs_diff_eq!(stats.avg_bp_iterations, 5.0, epsilon = 1e-10);
1255 assert_abs_diff_eq!(stats.block_error_rate, 0.0, epsilon = 1e-10);
1256
1257 stats.update_after_decoding(false, 8, 15.0);
1258 assert_eq!(stats.total_decodings, 2);
1259 assert_eq!(stats.successful_decodings, 1);
1260 assert_abs_diff_eq!(stats.avg_bp_iterations, 6.5, epsilon = 1e-10);
1261 assert_abs_diff_eq!(stats.block_error_rate, 0.5, epsilon = 1e-10);
1262 }
1263}