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 #[must_use]
150 pub fn new(id: usize) -> Self {
151 Self {
152 id,
153 connected_checks: Vec::new(),
154 belief: 0.0,
155 channel_llr: 0.0,
156 incoming_messages: HashMap::new(),
157 }
158 }
159
160 pub fn update_belief(&mut self) {
162 self.belief = self.channel_llr + self.incoming_messages.values().sum::<f64>();
163 }
164
165 #[must_use]
167 pub fn compute_outgoing_message(&self, check_id: usize) -> f64 {
168 let message_sum: f64 = self
169 .incoming_messages
170 .iter()
171 .filter(|(&id, _)| id != check_id)
172 .map(|(_, &value)| value)
173 .sum();
174 self.channel_llr + message_sum
175 }
176}
177
178impl CheckNode {
179 #[must_use]
181 pub fn new(id: usize) -> Self {
182 Self {
183 id,
184 connected_variables: Vec::new(),
185 incoming_messages: HashMap::new(),
186 syndrome: false,
187 }
188 }
189
190 #[must_use]
192 pub fn compute_outgoing_message_sum_product(&self, var_id: usize) -> f64 {
193 let product: f64 = self
194 .incoming_messages
195 .iter()
196 .filter(|(&id, _)| id != var_id)
197 .map(|(_, &msg)| msg.tanh() / 2.0)
198 .product();
199
200 2.0 * product.atanh()
201 }
202
203 #[must_use]
205 pub fn compute_outgoing_message_min_sum(&self, var_id: usize) -> f64 {
206 let other_messages: Vec<f64> = self
207 .incoming_messages
208 .iter()
209 .filter(|(&id, _)| id != var_id)
210 .map(|(_, &msg)| msg)
211 .collect();
212
213 if other_messages.is_empty() {
214 return 0.0;
215 }
216
217 let sign_product: f64 = other_messages
218 .iter()
219 .map(|&msg| if msg >= 0.0 { 1.0 } else { -1.0 })
220 .product();
221
222 let min_magnitude = other_messages
223 .iter()
224 .map(|&msg| msg.abs())
225 .fold(f64::INFINITY, f64::min);
226
227 sign_product * min_magnitude
228 }
229}
230
231pub struct QuantumLDPCCode {
233 config: LDPCConfig,
235 tanner_graph: TannerGraph,
237 #[allow(dead_code)]
239 x_stabilizers: Array2<u8>,
240 #[allow(dead_code)]
242 z_stabilizers: Array2<u8>,
243 #[allow(dead_code)]
245 logical_x_ops: Array2<u8>,
246 #[allow(dead_code)]
248 logical_z_ops: Array2<u8>,
249 #[allow(dead_code)]
251 circuit_interface: CircuitInterface,
252 stats: LDPCStats,
254}
255
256#[derive(Debug, Clone, Default, Serialize, Deserialize)]
258pub struct LDPCStats {
259 pub total_decodings: usize,
261 pub successful_decodings: usize,
263 pub avg_bp_iterations: f64,
265 pub block_error_rate: f64,
267 pub bit_error_rate: f64,
269 pub avg_decoding_time_ms: f64,
271 pub threshold_estimate: f64,
273 pub convergence_rate: f64,
275}
276
277impl LDPCStats {
278 pub fn update_after_decoding(&mut self, success: bool, iterations: usize, time_ms: f64) {
280 self.total_decodings += 1;
281 if success {
282 self.successful_decodings += 1;
283 }
284
285 let prev_avg_iter = self.avg_bp_iterations;
286 self.avg_bp_iterations = prev_avg_iter
287 .mul_add((self.total_decodings - 1) as f64, iterations as f64)
288 / self.total_decodings as f64;
289
290 let prev_avg_time = self.avg_decoding_time_ms;
291 self.avg_decoding_time_ms = prev_avg_time
292 .mul_add((self.total_decodings - 1) as f64, time_ms)
293 / self.total_decodings as f64;
294
295 self.block_error_rate =
296 1.0 - (self.successful_decodings as f64 / self.total_decodings as f64);
297 self.convergence_rate = self.successful_decodings as f64 / self.total_decodings as f64;
298 }
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct BPDecodingResult {
304 pub decoded_bits: Vec<bool>,
306 pub final_syndrome: Vec<bool>,
308 pub iterations: usize,
310 pub converged: bool,
312 pub final_llrs: Vec<f64>,
314 pub decoding_time_ms: f64,
316 pub success: bool,
318}
319
320impl QuantumLDPCCode {
321 pub fn new(config: LDPCConfig) -> Result<Self> {
323 let circuit_interface = CircuitInterface::new(Default::default())?;
324
325 let tanner_graph = Self::construct_tanner_graph(&config)?;
327
328 let (x_stabilizers, z_stabilizers) =
330 Self::generate_stabilizer_matrices(&config, &tanner_graph)?;
331
332 let (logical_x_ops, logical_z_ops) =
334 Self::generate_logical_operators(&config, &x_stabilizers, &z_stabilizers)?;
335
336 Ok(Self {
337 config,
338 tanner_graph,
339 x_stabilizers,
340 z_stabilizers,
341 logical_x_ops,
342 logical_z_ops,
343 circuit_interface,
344 stats: LDPCStats::default(),
345 })
346 }
347
348 fn construct_tanner_graph(config: &LDPCConfig) -> Result<TannerGraph> {
350 match config.construction_method {
351 LDPCConstructionMethod::RandomRegular => Self::construct_random_regular(config),
352 LDPCConstructionMethod::ProgressiveEdgeGrowth => Self::construct_peg(config),
353 LDPCConstructionMethod::Gallager => Self::construct_gallager(config),
354 LDPCConstructionMethod::MacKay => Self::construct_mackay(config),
355 LDPCConstructionMethod::QuantumBicycle => Self::construct_quantum_bicycle(config),
356 LDPCConstructionMethod::SurfaceCode => Self::construct_surface_code(config),
357 }
358 }
359
360 fn construct_random_regular(config: &LDPCConfig) -> Result<TannerGraph> {
362 let mut variable_nodes = Vec::with_capacity(config.n);
363 let mut check_nodes = Vec::with_capacity(config.m);
364 let mut adjacency_matrix = Array2::from_elem((config.n, config.m), false);
365
366 for i in 0..config.n {
368 variable_nodes.push(VariableNode::new(i));
369 }
370 for i in 0..config.m {
371 check_nodes.push(CheckNode::new(i));
372 }
373
374 let mut edges = Vec::new();
376
377 for var_id in 0..config.n {
379 for _ in 0..config.dv {
380 edges.push(var_id);
381 }
382 }
383
384 for i in 0..edges.len() {
386 let j = fastrand::usize(i..edges.len());
387 edges.swap(i, j);
388 }
389
390 let mut edge_idx = 0;
392 for check_id in 0..config.m {
393 for _ in 0..config.dc {
394 if edge_idx < edges.len() {
395 let var_id = edges[edge_idx];
396
397 variable_nodes[var_id].connected_checks.push(check_id);
399 check_nodes[check_id].connected_variables.push(var_id);
400 adjacency_matrix[[var_id, check_id]] = true;
401
402 edge_idx += 1;
403 }
404 }
405 }
406
407 let mut parity_check_matrix = Array2::zeros((config.m, config.n));
409 for (var_id, check_id) in adjacency_matrix.indexed_iter() {
410 if *check_id {
411 parity_check_matrix[[var_id.1, var_id.0]] = 1;
412 }
413 }
414
415 Ok(TannerGraph {
416 variable_nodes,
417 check_nodes,
418 adjacency_matrix,
419 parity_check_matrix,
420 })
421 }
422
423 fn construct_peg(config: &LDPCConfig) -> Result<TannerGraph> {
425 let mut variable_nodes = Vec::with_capacity(config.n);
426 let mut check_nodes = Vec::with_capacity(config.m);
427 let mut adjacency_matrix = Array2::from_elem((config.n, config.m), false);
428
429 for i in 0..config.n {
431 variable_nodes.push(VariableNode::new(i));
432 }
433 for i in 0..config.m {
434 check_nodes.push(CheckNode::new(i));
435 }
436
437 for var_id in 0..config.n {
439 let mut connected_checks = HashSet::new();
440
441 for _ in 0..config.dv {
442 let best_check = Self::find_best_check_for_peg(
443 var_id,
444 &connected_checks,
445 &variable_nodes,
446 &check_nodes,
447 &adjacency_matrix,
448 config.m,
449 );
450
451 if let Some(check_id) = best_check {
452 variable_nodes[var_id].connected_checks.push(check_id);
454 check_nodes[check_id].connected_variables.push(var_id);
455 adjacency_matrix[[var_id, check_id]] = true;
456 connected_checks.insert(check_id);
457 }
458 }
459 }
460
461 let mut parity_check_matrix = Array2::zeros((config.m, config.n));
463 for (var_id, check_id) in adjacency_matrix.indexed_iter() {
464 if *check_id {
465 parity_check_matrix[[var_id.1, var_id.0]] = 1;
466 }
467 }
468
469 Ok(TannerGraph {
470 variable_nodes,
471 check_nodes,
472 adjacency_matrix,
473 parity_check_matrix,
474 })
475 }
476
477 fn find_best_check_for_peg(
479 var_id: usize,
480 connected_checks: &HashSet<usize>,
481 _variable_nodes: &[VariableNode],
482 _check_nodes: &[CheckNode],
483 adjacency_matrix: &Array2<bool>,
484 num_checks: usize,
485 ) -> Option<usize> {
486 let mut best_check = None;
487 let mut best_girth = 0;
488
489 for check_id in 0..num_checks {
490 if connected_checks.contains(&check_id) {
491 continue;
492 }
493
494 let girth = Self::calculate_local_girth(var_id, check_id, adjacency_matrix);
496
497 if girth > best_girth {
498 best_girth = girth;
499 best_check = Some(check_id);
500 }
501 }
502
503 best_check
504 }
505
506 fn calculate_local_girth(
508 var_id: usize,
509 check_id: usize,
510 adjacency_matrix: &Array2<bool>,
511 ) -> usize {
512 let mut visited_vars = HashSet::new();
514 let mut visited_checks = HashSet::new();
515 let mut queue = VecDeque::new();
516
517 queue.push_back((var_id, 0, true)); visited_vars.insert(var_id);
519
520 while let Some((node_id, dist, is_var)) = queue.pop_front() {
521 if dist > 6 {
522 break;
524 }
525
526 if is_var {
527 for (check_idx, &connected) in adjacency_matrix.row(node_id).indexed_iter() {
529 if connected && check_idx != check_id {
530 if visited_checks.contains(&check_idx) {
531 return dist * 2; }
533 visited_checks.insert(check_idx);
534 queue.push_back((check_idx, dist + 1, false));
535 }
536 }
537 } else {
538 for (var_idx, &connected) in adjacency_matrix.column(node_id).indexed_iter() {
540 if connected && var_idx != var_id {
541 if visited_vars.contains(&var_idx) {
542 return dist * 2 + 1; }
544 visited_vars.insert(var_idx);
545 queue.push_back((var_idx, dist + 1, true));
546 }
547 }
548 }
549 }
550
551 12 }
553
554 fn construct_gallager(config: &LDPCConfig) -> Result<TannerGraph> {
556 Self::construct_random_regular(config)
559 }
560
561 fn construct_mackay(config: &LDPCConfig) -> Result<TannerGraph> {
563 Self::construct_peg(config)
566 }
567
568 fn construct_quantum_bicycle(config: &LDPCConfig) -> Result<TannerGraph> {
570 let mut variable_nodes = Vec::with_capacity(config.n);
572 let mut check_nodes = Vec::with_capacity(config.m);
573 let mut adjacency_matrix = Array2::from_elem((config.n, config.m), false);
574
575 for i in 0..config.n {
577 variable_nodes.push(VariableNode::new(i));
578 }
579 for i in 0..config.m {
580 check_nodes.push(CheckNode::new(i));
581 }
582
583 let l = config.n / 2;
585 for i in 0..l {
586 for j in 0..config.dv {
587 let check_id = (i + j * l / config.dv) % config.m;
588
589 variable_nodes[i].connected_checks.push(check_id);
591 check_nodes[check_id].connected_variables.push(i);
592 adjacency_matrix[[i, check_id]] = true;
593
594 if i + l < config.n {
596 variable_nodes[i + l].connected_checks.push(check_id);
597 check_nodes[check_id].connected_variables.push(i + l);
598 adjacency_matrix[[i + l, check_id]] = true;
599 }
600 }
601 }
602
603 let mut parity_check_matrix = Array2::zeros((config.m, config.n));
605 for (var_id, check_id) in adjacency_matrix.indexed_iter() {
606 if *check_id {
607 parity_check_matrix[[var_id.1, var_id.0]] = 1;
608 }
609 }
610
611 Ok(TannerGraph {
612 variable_nodes,
613 check_nodes,
614 adjacency_matrix,
615 parity_check_matrix,
616 })
617 }
618
619 fn construct_surface_code(config: &LDPCConfig) -> Result<TannerGraph> {
621 let d = (config.n as f64).sqrt() as usize; let mut variable_nodes = Vec::with_capacity(config.n);
624 let mut check_nodes = Vec::with_capacity(config.m);
625 let mut adjacency_matrix = Array2::from_elem((config.n, config.m), false);
626
627 for i in 0..config.n {
629 variable_nodes.push(VariableNode::new(i));
630 }
631 for i in 0..config.m {
632 check_nodes.push(CheckNode::new(i));
633 }
634
635 for check_id in 0..config.m {
637 let row = check_id / d;
638 let col = check_id % d;
639
640 let neighbors = [
642 (row.wrapping_sub(1), col),
643 (row + 1, col),
644 (row, col.wrapping_sub(1)),
645 (row, col + 1),
646 ];
647
648 for (r, c) in &neighbors {
649 if *r < d && *c < d {
650 let var_id = r * d + c;
651 if var_id < config.n {
652 variable_nodes[var_id].connected_checks.push(check_id);
653 check_nodes[check_id].connected_variables.push(var_id);
654 adjacency_matrix[[var_id, check_id]] = true;
655 }
656 }
657 }
658 }
659
660 let mut parity_check_matrix = Array2::zeros((config.m, config.n));
662 for (var_id, check_id) in adjacency_matrix.indexed_iter() {
663 if *check_id {
664 parity_check_matrix[[var_id.1, var_id.0]] = 1;
665 }
666 }
667
668 Ok(TannerGraph {
669 variable_nodes,
670 check_nodes,
671 adjacency_matrix,
672 parity_check_matrix,
673 })
674 }
675
676 fn generate_stabilizer_matrices(
678 _config: &LDPCConfig,
679 tanner_graph: &TannerGraph,
680 ) -> Result<(Array2<u8>, Array2<u8>)> {
681 let x_stabilizers = tanner_graph.parity_check_matrix.clone();
683 let z_stabilizers = tanner_graph.parity_check_matrix.clone();
684
685 Ok((x_stabilizers, z_stabilizers))
686 }
687
688 fn generate_logical_operators(
690 config: &LDPCConfig,
691 _x_stabilizers: &Array2<u8>,
692 _z_stabilizers: &Array2<u8>,
693 ) -> Result<(Array2<u8>, Array2<u8>)> {
694 let k = config.k;
695 let n = config.n;
696
697 let mut logical_x_ops = Array2::zeros((k, n));
699 let mut logical_z_ops = Array2::zeros((k, n));
700
701 for i in 0..k.min(n) {
703 logical_x_ops[[i, i]] = 1;
704 logical_z_ops[[i, i]] = 1;
705 }
706
707 Ok((logical_x_ops, logical_z_ops))
708 }
709
710 pub fn decode_belief_propagation(
712 &mut self,
713 received_llrs: &[f64],
714 syndrome: &[bool],
715 ) -> Result<BPDecodingResult> {
716 let start_time = std::time::Instant::now();
717
718 for (i, &llr) in received_llrs.iter().enumerate() {
720 if i < self.tanner_graph.variable_nodes.len() {
721 self.tanner_graph.variable_nodes[i].channel_llr = llr;
722 }
723 }
724
725 for (i, &syn) in syndrome.iter().enumerate() {
727 if i < self.tanner_graph.check_nodes.len() {
728 self.tanner_graph.check_nodes[i].syndrome = syn;
729 }
730 }
731
732 let mut converged = false;
733 let mut iteration = 0;
734
735 while iteration < self.config.max_bp_iterations && !converged {
737 self.update_variable_to_check_messages();
739
740 self.update_check_to_variable_messages();
742
743 self.update_variable_beliefs();
745
746 converged = self.check_convergence();
748
749 if self.config.early_termination && converged {
750 break;
751 }
752
753 iteration += 1;
754 }
755
756 let decoded_bits = self.extract_decoded_bits();
758
759 let final_syndrome = self.calculate_syndrome(&decoded_bits);
761
762 let success = final_syndrome.iter().all(|&s| !s);
764
765 let decoding_time_ms = start_time.elapsed().as_secs_f64() * 1000.0;
766
767 self.stats
769 .update_after_decoding(success, iteration, decoding_time_ms);
770
771 let final_llrs: Vec<f64> = self
773 .tanner_graph
774 .variable_nodes
775 .iter()
776 .map(|node| node.belief)
777 .collect();
778
779 Ok(BPDecodingResult {
780 decoded_bits,
781 final_syndrome,
782 iterations: iteration,
783 converged,
784 final_llrs,
785 decoding_time_ms,
786 success,
787 })
788 }
789
790 fn update_variable_to_check_messages(&mut self) {
792 for var_node in &mut self.tanner_graph.variable_nodes {
793 for &check_id in &var_node.connected_checks.clone() {
794 let message = var_node.compute_outgoing_message(check_id);
795
796 let old_message = self.tanner_graph.check_nodes[check_id]
798 .incoming_messages
799 .get(&var_node.id)
800 .unwrap_or(&0.0);
801
802 let damped_message = self
803 .config
804 .damping_factor
805 .mul_add(message, (1.0 - self.config.damping_factor) * old_message);
806
807 self.tanner_graph.check_nodes[check_id]
808 .incoming_messages
809 .insert(var_node.id, damped_message);
810 }
811 }
812 }
813
814 fn update_check_to_variable_messages(&mut self) {
816 for check_node in &mut self.tanner_graph.check_nodes {
817 for &var_id in &check_node.connected_variables.clone() {
818 let message = match self.config.bp_algorithm {
819 BeliefPropagationAlgorithm::SumProduct => {
820 check_node.compute_outgoing_message_sum_product(var_id)
821 }
822 BeliefPropagationAlgorithm::MinSum => {
823 check_node.compute_outgoing_message_min_sum(var_id)
824 }
825 BeliefPropagationAlgorithm::NormalizedMinSum => {
826 let min_sum_msg = check_node.compute_outgoing_message_min_sum(var_id);
827 min_sum_msg * 0.75 }
829 BeliefPropagationAlgorithm::OffsetMinSum => {
830 let min_sum_msg = check_node.compute_outgoing_message_min_sum(var_id);
831 let offset = 0.1;
832 if min_sum_msg.abs() > offset {
833 min_sum_msg.signum() * (min_sum_msg.abs() - offset)
834 } else {
835 0.0
836 }
837 }
838 BeliefPropagationAlgorithm::LayeredBP => {
839 check_node.compute_outgoing_message_sum_product(var_id)
841 }
842 };
843
844 let old_message = self.tanner_graph.variable_nodes[var_id]
846 .incoming_messages
847 .get(&check_node.id)
848 .unwrap_or(&0.0);
849
850 let damped_message = self
851 .config
852 .damping_factor
853 .mul_add(message, (1.0 - self.config.damping_factor) * old_message);
854
855 self.tanner_graph.variable_nodes[var_id]
856 .incoming_messages
857 .insert(check_node.id, damped_message);
858 }
859 }
860 }
861
862 fn update_variable_beliefs(&mut self) {
864 for var_node in &mut self.tanner_graph.variable_nodes {
865 var_node.update_belief();
866 }
867 }
868
869 fn check_convergence(&self) -> bool {
871 let decoded_bits = self.extract_decoded_bits();
873 let syndrome = self.calculate_syndrome(&decoded_bits);
874
875 syndrome.iter().all(|&s| !s)
876 }
877
878 fn extract_decoded_bits(&self) -> Vec<bool> {
880 self.tanner_graph
881 .variable_nodes
882 .iter()
883 .map(|node| node.belief < 0.0)
884 .collect()
885 }
886
887 fn calculate_syndrome(&self, codeword: &[bool]) -> Vec<bool> {
889 let mut syndrome = vec![false; self.tanner_graph.check_nodes.len()];
890
891 for (check_id, check_node) in self.tanner_graph.check_nodes.iter().enumerate() {
892 let mut parity = false;
893 for &var_id in &check_node.connected_variables {
894 if var_id < codeword.len() && codeword[var_id] {
895 parity = !parity;
896 }
897 }
898 syndrome[check_id] = parity;
899 }
900
901 syndrome
902 }
903
904 pub fn syndrome_circuit(&self) -> Result<InterfaceCircuit> {
906 let num_data_qubits = self.config.n;
907 let num_syndrome_qubits = self.config.m;
908 let total_qubits = num_data_qubits + num_syndrome_qubits;
909
910 let mut circuit = InterfaceCircuit::new(total_qubits, num_syndrome_qubits);
911
912 for (check_id, check_node) in self.tanner_graph.check_nodes.iter().enumerate() {
914 let syndrome_qubit = num_data_qubits + check_id;
915
916 for &var_id in &check_node.connected_variables {
917 if var_id < num_data_qubits {
918 circuit.add_gate(InterfaceGate::new(
919 InterfaceGateType::CNOT,
920 vec![var_id, syndrome_qubit],
921 ));
922 }
923 }
924 }
925
926 Ok(circuit)
927 }
928
929 pub fn estimate_threshold(
931 &mut self,
932 noise_range: (f64, f64),
933 num_trials: usize,
934 ) -> Result<f64> {
935 let (min_noise, max_noise) = noise_range;
936 let mut threshold = f64::midpoint(min_noise, max_noise);
937 let mut search_range = max_noise - min_noise;
938
939 while search_range > 0.001 {
941 self.config.noise_variance = threshold;
942
943 let mut successes = 0;
944 for _ in 0..num_trials {
945 let errors: Vec<bool> = (0..self.config.n)
947 .map(|_| fastrand::f64() < threshold)
948 .collect();
949
950 let llrs: Vec<f64> = errors
952 .iter()
953 .map(|&error| {
954 if error {
955 -2.0 / self.config.noise_variance
956 } else {
957 2.0 / self.config.noise_variance
958 }
959 })
960 .collect();
961
962 let syndrome = self.calculate_syndrome(&errors);
964
965 if let Ok(result) = self.decode_belief_propagation(&llrs, &syndrome) {
967 if result.success {
968 successes += 1;
969 }
970 }
971 }
972
973 let success_rate = f64::from(successes) / num_trials as f64;
974
975 if success_rate > 0.5 {
976 threshold = f64::midpoint(threshold, max_noise);
977 } else {
978 threshold = f64::midpoint(min_noise, threshold);
979 }
980
981 search_range /= 2.0;
982 }
983
984 self.stats.threshold_estimate = threshold;
985 Ok(threshold)
986 }
987
988 #[must_use]
990 pub const fn get_stats(&self) -> &LDPCStats {
991 &self.stats
992 }
993
994 pub fn reset_stats(&mut self) {
996 self.stats = LDPCStats::default();
997 }
998
999 #[must_use]
1001 pub const fn get_parameters(&self) -> (usize, usize, usize) {
1002 (self.config.n, self.config.k, self.config.m)
1003 }
1004
1005 #[must_use]
1007 pub const fn get_tanner_graph(&self) -> &TannerGraph {
1008 &self.tanner_graph
1009 }
1010}
1011
1012pub fn benchmark_quantum_ldpc_codes() -> Result<HashMap<String, f64>> {
1014 let mut results = HashMap::new();
1015
1016 let configs = vec![
1018 LDPCConfig {
1019 k: 10,
1020 n: 20,
1021 m: 10,
1022 construction_method: LDPCConstructionMethod::RandomRegular,
1023 bp_algorithm: BeliefPropagationAlgorithm::SumProduct,
1024 ..Default::default()
1025 },
1026 LDPCConfig {
1027 k: 15,
1028 n: 30,
1029 m: 15,
1030 construction_method: LDPCConstructionMethod::ProgressiveEdgeGrowth,
1031 bp_algorithm: BeliefPropagationAlgorithm::MinSum,
1032 ..Default::default()
1033 },
1034 LDPCConfig {
1035 k: 20,
1036 n: 40,
1037 m: 20,
1038 construction_method: LDPCConstructionMethod::QuantumBicycle,
1039 bp_algorithm: BeliefPropagationAlgorithm::NormalizedMinSum,
1040 ..Default::default()
1041 },
1042 ];
1043
1044 for (i, config) in configs.into_iter().enumerate() {
1045 let start = std::time::Instant::now();
1046
1047 let mut ldpc_code = QuantumLDPCCode::new(config)?;
1048
1049 for _ in 0..50 {
1051 let errors: Vec<bool> = (0..ldpc_code.config.n)
1053 .map(|_| fastrand::f64() < 0.05)
1054 .collect();
1055
1056 let llrs: Vec<f64> = errors
1058 .iter()
1059 .map(|&error| if error { -1.0 } else { 1.0 })
1060 .collect();
1061
1062 let syndrome = ldpc_code.calculate_syndrome(&errors);
1064
1065 let _result = ldpc_code.decode_belief_propagation(&llrs, &syndrome)?;
1067 }
1068
1069 let time = start.elapsed().as_secs_f64() * 1000.0;
1070 results.insert(format!("config_{i}"), time);
1071
1072 let stats = ldpc_code.get_stats();
1074 results.insert(format!("config_{i}_success_rate"), stats.convergence_rate);
1075 results.insert(
1076 format!("config_{i}_avg_iterations"),
1077 stats.avg_bp_iterations,
1078 );
1079 }
1080
1081 Ok(results)
1082}
1083
1084#[cfg(test)]
1085mod tests {
1086 use super::*;
1087 use approx::assert_abs_diff_eq;
1088
1089 #[test]
1090 fn test_ldpc_code_creation() {
1091 let config = LDPCConfig::default();
1092 let ldpc_code = QuantumLDPCCode::new(config);
1093 assert!(ldpc_code.is_ok());
1094 }
1095
1096 #[test]
1097 fn test_tanner_graph_construction() {
1098 let config = LDPCConfig {
1099 k: 5,
1100 n: 10,
1101 m: 5,
1102 dv: 2,
1103 dc: 4,
1104 ..Default::default()
1105 };
1106
1107 let tanner_graph = QuantumLDPCCode::construct_random_regular(&config);
1108 assert!(tanner_graph.is_ok());
1109
1110 let graph = tanner_graph.expect("tanner_graph construction should succeed");
1111 assert_eq!(graph.variable_nodes.len(), 10);
1112 assert_eq!(graph.check_nodes.len(), 5);
1113 }
1114
1115 #[test]
1116 fn test_variable_node_operations() {
1117 let mut var_node = VariableNode::new(0);
1118 var_node.channel_llr = 1.0;
1119 var_node.incoming_messages.insert(1, 0.5);
1120 var_node.incoming_messages.insert(2, -0.3);
1121
1122 var_node.update_belief();
1123 assert_abs_diff_eq!(var_node.belief, 1.2, epsilon = 1e-10);
1124
1125 let outgoing = var_node.compute_outgoing_message(1);
1126 assert_abs_diff_eq!(outgoing, 0.7, epsilon = 1e-10);
1127 }
1128
1129 #[test]
1130 fn test_check_node_operations() {
1131 let mut check_node = CheckNode::new(0);
1132 check_node.incoming_messages.insert(1, 0.8);
1133 check_node.incoming_messages.insert(2, -0.6);
1134 check_node.incoming_messages.insert(3, 1.2);
1135
1136 let sum_product_msg = check_node.compute_outgoing_message_sum_product(1);
1137 assert!(sum_product_msg.is_finite());
1138
1139 let min_sum_msg = check_node.compute_outgoing_message_min_sum(1);
1140 assert_abs_diff_eq!(min_sum_msg.abs(), 0.6, epsilon = 1e-10);
1141 }
1142
1143 #[test]
1144 fn test_belief_propagation_decoding() {
1145 let config = LDPCConfig {
1146 k: 3,
1147 n: 6,
1148 m: 3,
1149 max_bp_iterations: 10,
1150 ..Default::default()
1151 };
1152
1153 let mut ldpc_code =
1154 QuantumLDPCCode::new(config).expect("LDPC code creation should succeed");
1155
1156 let llrs = vec![1.0, -1.0, 1.0, 1.0, -1.0, 1.0];
1157 let syndrome = vec![false, true, false];
1158
1159 let result = ldpc_code.decode_belief_propagation(&llrs, &syndrome);
1160 assert!(result.is_ok());
1161
1162 let bp_result = result.expect("decode_belief_propagation should succeed");
1163 assert_eq!(bp_result.decoded_bits.len(), 6);
1164 assert!(bp_result.iterations <= 10);
1165 }
1166
1167 #[test]
1168 fn test_syndrome_calculation() {
1169 let config = LDPCConfig {
1170 k: 2,
1171 n: 4,
1172 m: 2,
1173 ..Default::default()
1174 };
1175
1176 let ldpc_code = QuantumLDPCCode::new(config).expect("LDPC code creation should succeed");
1177
1178 let codeword = vec![false, true, false, true];
1179 let syndrome = ldpc_code.calculate_syndrome(&codeword);
1180
1181 assert_eq!(syndrome.len(), 2);
1182 }
1183
1184 #[test]
1185 fn test_syndrome_circuit_generation() {
1186 let config = LDPCConfig {
1187 k: 3,
1188 n: 6,
1189 m: 3,
1190 ..Default::default()
1191 };
1192
1193 let ldpc_code = QuantumLDPCCode::new(config).expect("LDPC code creation should succeed");
1194 let circuit = ldpc_code.syndrome_circuit();
1195
1196 assert!(circuit.is_ok());
1197 let syndrome_circuit = circuit.expect("syndrome_circuit should succeed");
1198 assert_eq!(syndrome_circuit.num_qubits, 9); }
1200
1201 #[test]
1202 fn test_different_construction_methods() {
1203 let base_config = LDPCConfig {
1204 k: 3,
1205 n: 6,
1206 m: 3,
1207 ..Default::default()
1208 };
1209
1210 let methods = vec![
1211 LDPCConstructionMethod::RandomRegular,
1212 LDPCConstructionMethod::ProgressiveEdgeGrowth,
1213 LDPCConstructionMethod::QuantumBicycle,
1214 LDPCConstructionMethod::SurfaceCode,
1215 ];
1216
1217 for method in methods {
1218 let mut config = base_config.clone();
1219 config.construction_method = method;
1220
1221 let ldpc_code = QuantumLDPCCode::new(config);
1222 assert!(ldpc_code.is_ok(), "Failed for method: {method:?}");
1223 }
1224 }
1225
1226 #[test]
1227 fn test_different_bp_algorithms() {
1228 let base_config = LDPCConfig {
1229 k: 3,
1230 n: 6,
1231 m: 3,
1232 ..Default::default()
1233 };
1234
1235 let algorithms = vec![
1236 BeliefPropagationAlgorithm::SumProduct,
1237 BeliefPropagationAlgorithm::MinSum,
1238 BeliefPropagationAlgorithm::NormalizedMinSum,
1239 BeliefPropagationAlgorithm::OffsetMinSum,
1240 ];
1241
1242 for algorithm in algorithms {
1243 let mut config = base_config.clone();
1244 config.bp_algorithm = algorithm;
1245
1246 let mut ldpc_code =
1247 QuantumLDPCCode::new(config).expect("LDPC code creation should succeed");
1248
1249 let llrs = vec![1.0, -1.0, 1.0, 1.0, -1.0, 1.0];
1250 let syndrome = vec![false, true, false];
1251
1252 let result = ldpc_code.decode_belief_propagation(&llrs, &syndrome);
1253 assert!(result.is_ok(), "Failed for algorithm: {algorithm:?}");
1254 }
1255 }
1256
1257 #[test]
1258 fn test_stats_updates() {
1259 let mut stats = LDPCStats::default();
1260
1261 stats.update_after_decoding(true, 5, 10.0);
1262 assert_eq!(stats.total_decodings, 1);
1263 assert_eq!(stats.successful_decodings, 1);
1264 assert_abs_diff_eq!(stats.avg_bp_iterations, 5.0, epsilon = 1e-10);
1265 assert_abs_diff_eq!(stats.block_error_rate, 0.0, epsilon = 1e-10);
1266
1267 stats.update_after_decoding(false, 8, 15.0);
1268 assert_eq!(stats.total_decodings, 2);
1269 assert_eq!(stats.successful_decodings, 1);
1270 assert_abs_diff_eq!(stats.avg_bp_iterations, 6.5, epsilon = 1e-10);
1271 assert_abs_diff_eq!(stats.block_error_rate, 0.5, epsilon = 1e-10);
1272 }
1273}