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 * (self.total_decodings - 1) as f64
282 + 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 * (self.total_decodings - 1) as f64 + time_ms)
287 / self.total_decodings as f64;
288
289 self.block_error_rate =
290 1.0 - (self.successful_decodings as f64 / self.total_decodings as f64);
291 self.convergence_rate = self.successful_decodings as f64 / self.total_decodings as f64;
292 }
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize)]
297pub struct BPDecodingResult {
298 pub decoded_bits: Vec<bool>,
300 pub final_syndrome: Vec<bool>,
302 pub iterations: usize,
304 pub converged: bool,
306 pub final_llrs: Vec<f64>,
308 pub decoding_time_ms: f64,
310 pub success: bool,
312}
313
314impl QuantumLDPCCode {
315 pub fn new(config: LDPCConfig) -> Result<Self> {
317 let circuit_interface = CircuitInterface::new(Default::default())?;
318
319 let tanner_graph = Self::construct_tanner_graph(&config)?;
321
322 let (x_stabilizers, z_stabilizers) =
324 Self::generate_stabilizer_matrices(&config, &tanner_graph)?;
325
326 let (logical_x_ops, logical_z_ops) =
328 Self::generate_logical_operators(&config, &x_stabilizers, &z_stabilizers)?;
329
330 Ok(Self {
331 config,
332 tanner_graph,
333 x_stabilizers,
334 z_stabilizers,
335 logical_x_ops,
336 logical_z_ops,
337 circuit_interface,
338 stats: LDPCStats::default(),
339 })
340 }
341
342 fn construct_tanner_graph(config: &LDPCConfig) -> Result<TannerGraph> {
344 match config.construction_method {
345 LDPCConstructionMethod::RandomRegular => Self::construct_random_regular(config),
346 LDPCConstructionMethod::ProgressiveEdgeGrowth => Self::construct_peg(config),
347 LDPCConstructionMethod::Gallager => Self::construct_gallager(config),
348 LDPCConstructionMethod::MacKay => Self::construct_mackay(config),
349 LDPCConstructionMethod::QuantumBicycle => Self::construct_quantum_bicycle(config),
350 LDPCConstructionMethod::SurfaceCode => Self::construct_surface_code(config),
351 }
352 }
353
354 fn construct_random_regular(config: &LDPCConfig) -> Result<TannerGraph> {
356 let mut variable_nodes = Vec::with_capacity(config.n);
357 let mut check_nodes = Vec::with_capacity(config.m);
358 let mut adjacency_matrix = Array2::from_elem((config.n, config.m), false);
359
360 for i in 0..config.n {
362 variable_nodes.push(VariableNode::new(i));
363 }
364 for i in 0..config.m {
365 check_nodes.push(CheckNode::new(i));
366 }
367
368 let mut edges = Vec::new();
370
371 for var_id in 0..config.n {
373 for _ in 0..config.dv {
374 edges.push(var_id);
375 }
376 }
377
378 for i in 0..edges.len() {
380 let j = fastrand::usize(i..edges.len());
381 edges.swap(i, j);
382 }
383
384 let mut edge_idx = 0;
386 for check_id in 0..config.m {
387 for _ in 0..config.dc {
388 if edge_idx < edges.len() {
389 let var_id = edges[edge_idx];
390
391 variable_nodes[var_id].connected_checks.push(check_id);
393 check_nodes[check_id].connected_variables.push(var_id);
394 adjacency_matrix[[var_id, check_id]] = true;
395
396 edge_idx += 1;
397 }
398 }
399 }
400
401 let mut parity_check_matrix = Array2::zeros((config.m, config.n));
403 for (var_id, check_id) in adjacency_matrix.indexed_iter() {
404 if *check_id {
405 parity_check_matrix[[var_id.1, var_id.0]] = 1;
406 }
407 }
408
409 Ok(TannerGraph {
410 variable_nodes,
411 check_nodes,
412 adjacency_matrix,
413 parity_check_matrix,
414 })
415 }
416
417 fn construct_peg(config: &LDPCConfig) -> Result<TannerGraph> {
419 let mut variable_nodes = Vec::with_capacity(config.n);
420 let mut check_nodes = Vec::with_capacity(config.m);
421 let mut adjacency_matrix = Array2::from_elem((config.n, config.m), false);
422
423 for i in 0..config.n {
425 variable_nodes.push(VariableNode::new(i));
426 }
427 for i in 0..config.m {
428 check_nodes.push(CheckNode::new(i));
429 }
430
431 for var_id in 0..config.n {
433 let mut connected_checks = HashSet::new();
434
435 for _ in 0..config.dv {
436 let best_check = Self::find_best_check_for_peg(
437 var_id,
438 &connected_checks,
439 &variable_nodes,
440 &check_nodes,
441 &adjacency_matrix,
442 config.m,
443 );
444
445 if let Some(check_id) = best_check {
446 variable_nodes[var_id].connected_checks.push(check_id);
448 check_nodes[check_id].connected_variables.push(var_id);
449 adjacency_matrix[[var_id, check_id]] = true;
450 connected_checks.insert(check_id);
451 }
452 }
453 }
454
455 let mut parity_check_matrix = Array2::zeros((config.m, config.n));
457 for (var_id, check_id) in adjacency_matrix.indexed_iter() {
458 if *check_id {
459 parity_check_matrix[[var_id.1, var_id.0]] = 1;
460 }
461 }
462
463 Ok(TannerGraph {
464 variable_nodes,
465 check_nodes,
466 adjacency_matrix,
467 parity_check_matrix,
468 })
469 }
470
471 fn find_best_check_for_peg(
473 var_id: usize,
474 connected_checks: &HashSet<usize>,
475 _variable_nodes: &[VariableNode],
476 _check_nodes: &[CheckNode],
477 adjacency_matrix: &Array2<bool>,
478 num_checks: usize,
479 ) -> Option<usize> {
480 let mut best_check = None;
481 let mut best_girth = 0;
482
483 for check_id in 0..num_checks {
484 if connected_checks.contains(&check_id) {
485 continue;
486 }
487
488 let girth = Self::calculate_local_girth(var_id, check_id, adjacency_matrix);
490
491 if girth > best_girth {
492 best_girth = girth;
493 best_check = Some(check_id);
494 }
495 }
496
497 best_check
498 }
499
500 fn calculate_local_girth(
502 var_id: usize,
503 check_id: usize,
504 adjacency_matrix: &Array2<bool>,
505 ) -> usize {
506 let mut visited_vars = HashSet::new();
508 let mut visited_checks = HashSet::new();
509 let mut queue = VecDeque::new();
510
511 queue.push_back((var_id, 0, true)); visited_vars.insert(var_id);
513
514 while let Some((node_id, dist, is_var)) = queue.pop_front() {
515 if dist > 6 {
516 break;
518 }
519
520 if is_var {
521 for (check_idx, &connected) in adjacency_matrix.row(node_id).indexed_iter() {
523 if connected && check_idx != check_id {
524 if visited_checks.contains(&check_idx) {
525 return dist * 2; }
527 visited_checks.insert(check_idx);
528 queue.push_back((check_idx, dist + 1, false));
529 }
530 }
531 } else {
532 for (var_idx, &connected) in adjacency_matrix.column(node_id).indexed_iter() {
534 if connected && var_idx != var_id {
535 if visited_vars.contains(&var_idx) {
536 return dist * 2 + 1; }
538 visited_vars.insert(var_idx);
539 queue.push_back((var_idx, dist + 1, true));
540 }
541 }
542 }
543 }
544
545 12 }
547
548 fn construct_gallager(config: &LDPCConfig) -> Result<TannerGraph> {
550 Self::construct_random_regular(config)
553 }
554
555 fn construct_mackay(config: &LDPCConfig) -> Result<TannerGraph> {
557 Self::construct_peg(config)
560 }
561
562 fn construct_quantum_bicycle(config: &LDPCConfig) -> Result<TannerGraph> {
564 let mut variable_nodes = Vec::with_capacity(config.n);
566 let mut check_nodes = Vec::with_capacity(config.m);
567 let mut adjacency_matrix = Array2::from_elem((config.n, config.m), false);
568
569 for i in 0..config.n {
571 variable_nodes.push(VariableNode::new(i));
572 }
573 for i in 0..config.m {
574 check_nodes.push(CheckNode::new(i));
575 }
576
577 let l = config.n / 2;
579 for i in 0..l {
580 for j in 0..config.dv {
581 let check_id = (i + j * l / config.dv) % config.m;
582
583 variable_nodes[i].connected_checks.push(check_id);
585 check_nodes[check_id].connected_variables.push(i);
586 adjacency_matrix[[i, check_id]] = true;
587
588 if i + l < config.n {
590 variable_nodes[i + l].connected_checks.push(check_id);
591 check_nodes[check_id].connected_variables.push(i + l);
592 adjacency_matrix[[i + l, check_id]] = true;
593 }
594 }
595 }
596
597 let mut parity_check_matrix = Array2::zeros((config.m, config.n));
599 for (var_id, check_id) in adjacency_matrix.indexed_iter() {
600 if *check_id {
601 parity_check_matrix[[var_id.1, var_id.0]] = 1;
602 }
603 }
604
605 Ok(TannerGraph {
606 variable_nodes,
607 check_nodes,
608 adjacency_matrix,
609 parity_check_matrix,
610 })
611 }
612
613 fn construct_surface_code(config: &LDPCConfig) -> Result<TannerGraph> {
615 let d = (config.n as f64).sqrt() as usize; let mut variable_nodes = Vec::with_capacity(config.n);
618 let mut check_nodes = Vec::with_capacity(config.m);
619 let mut adjacency_matrix = Array2::from_elem((config.n, config.m), false);
620
621 for i in 0..config.n {
623 variable_nodes.push(VariableNode::new(i));
624 }
625 for i in 0..config.m {
626 check_nodes.push(CheckNode::new(i));
627 }
628
629 for check_id in 0..config.m {
631 let row = check_id / d;
632 let col = check_id % d;
633
634 let neighbors = [
636 (row.wrapping_sub(1), col),
637 (row + 1, col),
638 (row, col.wrapping_sub(1)),
639 (row, col + 1),
640 ];
641
642 for (r, c) in neighbors.iter() {
643 if *r < d && *c < d {
644 let var_id = r * d + c;
645 if var_id < config.n {
646 variable_nodes[var_id].connected_checks.push(check_id);
647 check_nodes[check_id].connected_variables.push(var_id);
648 adjacency_matrix[[var_id, check_id]] = true;
649 }
650 }
651 }
652 }
653
654 let mut parity_check_matrix = Array2::zeros((config.m, config.n));
656 for (var_id, check_id) in adjacency_matrix.indexed_iter() {
657 if *check_id {
658 parity_check_matrix[[var_id.1, var_id.0]] = 1;
659 }
660 }
661
662 Ok(TannerGraph {
663 variable_nodes,
664 check_nodes,
665 adjacency_matrix,
666 parity_check_matrix,
667 })
668 }
669
670 fn generate_stabilizer_matrices(
672 _config: &LDPCConfig,
673 tanner_graph: &TannerGraph,
674 ) -> Result<(Array2<u8>, Array2<u8>)> {
675 let x_stabilizers = tanner_graph.parity_check_matrix.clone();
677 let z_stabilizers = tanner_graph.parity_check_matrix.clone();
678
679 Ok((x_stabilizers, z_stabilizers))
680 }
681
682 fn generate_logical_operators(
684 config: &LDPCConfig,
685 _x_stabilizers: &Array2<u8>,
686 _z_stabilizers: &Array2<u8>,
687 ) -> Result<(Array2<u8>, Array2<u8>)> {
688 let k = config.k;
689 let n = config.n;
690
691 let mut logical_x_ops = Array2::zeros((k, n));
693 let mut logical_z_ops = Array2::zeros((k, n));
694
695 for i in 0..k.min(n) {
697 logical_x_ops[[i, i]] = 1;
698 logical_z_ops[[i, i]] = 1;
699 }
700
701 Ok((logical_x_ops, logical_z_ops))
702 }
703
704 pub fn decode_belief_propagation(
706 &mut self,
707 received_llrs: &[f64],
708 syndrome: &[bool],
709 ) -> Result<BPDecodingResult> {
710 let start_time = std::time::Instant::now();
711
712 for (i, &llr) in received_llrs.iter().enumerate() {
714 if i < self.tanner_graph.variable_nodes.len() {
715 self.tanner_graph.variable_nodes[i].channel_llr = llr;
716 }
717 }
718
719 for (i, &syn) in syndrome.iter().enumerate() {
721 if i < self.tanner_graph.check_nodes.len() {
722 self.tanner_graph.check_nodes[i].syndrome = syn;
723 }
724 }
725
726 let mut converged = false;
727 let mut iteration = 0;
728
729 while iteration < self.config.max_bp_iterations && !converged {
731 self.update_variable_to_check_messages();
733
734 self.update_check_to_variable_messages();
736
737 self.update_variable_beliefs();
739
740 converged = self.check_convergence();
742
743 if self.config.early_termination && converged {
744 break;
745 }
746
747 iteration += 1;
748 }
749
750 let decoded_bits = self.extract_decoded_bits();
752
753 let final_syndrome = self.calculate_syndrome(&decoded_bits);
755
756 let success = final_syndrome.iter().all(|&s| !s);
758
759 let decoding_time_ms = start_time.elapsed().as_secs_f64() * 1000.0;
760
761 self.stats
763 .update_after_decoding(success, iteration, decoding_time_ms);
764
765 let final_llrs: Vec<f64> = self
767 .tanner_graph
768 .variable_nodes
769 .iter()
770 .map(|node| node.belief)
771 .collect();
772
773 Ok(BPDecodingResult {
774 decoded_bits,
775 final_syndrome,
776 iterations: iteration,
777 converged,
778 final_llrs,
779 decoding_time_ms,
780 success,
781 })
782 }
783
784 fn update_variable_to_check_messages(&mut self) {
786 for var_node in &mut self.tanner_graph.variable_nodes {
787 for &check_id in &var_node.connected_checks.clone() {
788 let message = var_node.compute_outgoing_message(check_id);
789
790 let old_message = self.tanner_graph.check_nodes[check_id]
792 .incoming_messages
793 .get(&var_node.id)
794 .unwrap_or(&0.0);
795
796 let damped_message = self.config.damping_factor * message
797 + (1.0 - self.config.damping_factor) * old_message;
798
799 self.tanner_graph.check_nodes[check_id]
800 .incoming_messages
801 .insert(var_node.id, damped_message);
802 }
803 }
804 }
805
806 fn update_check_to_variable_messages(&mut self) {
808 for check_node in &mut self.tanner_graph.check_nodes {
809 for &var_id in &check_node.connected_variables.clone() {
810 let message = match self.config.bp_algorithm {
811 BeliefPropagationAlgorithm::SumProduct => {
812 check_node.compute_outgoing_message_sum_product(var_id)
813 }
814 BeliefPropagationAlgorithm::MinSum => {
815 check_node.compute_outgoing_message_min_sum(var_id)
816 }
817 BeliefPropagationAlgorithm::NormalizedMinSum => {
818 let min_sum_msg = check_node.compute_outgoing_message_min_sum(var_id);
819 min_sum_msg * 0.75 }
821 BeliefPropagationAlgorithm::OffsetMinSum => {
822 let min_sum_msg = check_node.compute_outgoing_message_min_sum(var_id);
823 let offset = 0.1;
824 if min_sum_msg.abs() > offset {
825 min_sum_msg.signum() * (min_sum_msg.abs() - offset)
826 } else {
827 0.0
828 }
829 }
830 BeliefPropagationAlgorithm::LayeredBP => {
831 check_node.compute_outgoing_message_sum_product(var_id)
833 }
834 };
835
836 let old_message = self.tanner_graph.variable_nodes[var_id]
838 .incoming_messages
839 .get(&check_node.id)
840 .unwrap_or(&0.0);
841
842 let damped_message = self.config.damping_factor * message
843 + (1.0 - self.config.damping_factor) * old_message;
844
845 self.tanner_graph.variable_nodes[var_id]
846 .incoming_messages
847 .insert(check_node.id, damped_message);
848 }
849 }
850 }
851
852 fn update_variable_beliefs(&mut self) {
854 for var_node in &mut self.tanner_graph.variable_nodes {
855 var_node.update_belief();
856 }
857 }
858
859 fn check_convergence(&self) -> bool {
861 let decoded_bits = self.extract_decoded_bits();
863 let syndrome = self.calculate_syndrome(&decoded_bits);
864
865 syndrome.iter().all(|&s| !s)
866 }
867
868 fn extract_decoded_bits(&self) -> Vec<bool> {
870 self.tanner_graph
871 .variable_nodes
872 .iter()
873 .map(|node| node.belief < 0.0)
874 .collect()
875 }
876
877 fn calculate_syndrome(&self, codeword: &[bool]) -> Vec<bool> {
879 let mut syndrome = vec![false; self.tanner_graph.check_nodes.len()];
880
881 for (check_id, check_node) in self.tanner_graph.check_nodes.iter().enumerate() {
882 let mut parity = false;
883 for &var_id in &check_node.connected_variables {
884 if var_id < codeword.len() && codeword[var_id] {
885 parity = !parity;
886 }
887 }
888 syndrome[check_id] = parity;
889 }
890
891 syndrome
892 }
893
894 pub fn syndrome_circuit(&self) -> Result<InterfaceCircuit> {
896 let num_data_qubits = self.config.n;
897 let num_syndrome_qubits = self.config.m;
898 let total_qubits = num_data_qubits + num_syndrome_qubits;
899
900 let mut circuit = InterfaceCircuit::new(total_qubits, num_syndrome_qubits);
901
902 for (check_id, check_node) in self.tanner_graph.check_nodes.iter().enumerate() {
904 let syndrome_qubit = num_data_qubits + check_id;
905
906 for &var_id in &check_node.connected_variables {
907 if var_id < num_data_qubits {
908 circuit.add_gate(InterfaceGate::new(
909 InterfaceGateType::CNOT,
910 vec![var_id, syndrome_qubit],
911 ));
912 }
913 }
914 }
915
916 Ok(circuit)
917 }
918
919 pub fn estimate_threshold(
921 &mut self,
922 noise_range: (f64, f64),
923 num_trials: usize,
924 ) -> Result<f64> {
925 let (min_noise, max_noise) = noise_range;
926 let mut threshold = (min_noise + max_noise) / 2.0;
927 let mut search_range = max_noise - min_noise;
928
929 while search_range > 0.001 {
931 self.config.noise_variance = threshold;
932
933 let mut successes = 0;
934 for _ in 0..num_trials {
935 let errors: Vec<bool> = (0..self.config.n)
937 .map(|_| fastrand::f64() < threshold)
938 .collect();
939
940 let llrs: Vec<f64> = errors
942 .iter()
943 .map(|&error| {
944 if error {
945 -2.0 / self.config.noise_variance
946 } else {
947 2.0 / self.config.noise_variance
948 }
949 })
950 .collect();
951
952 let syndrome = self.calculate_syndrome(&errors);
954
955 if let Ok(result) = self.decode_belief_propagation(&llrs, &syndrome) {
957 if result.success {
958 successes += 1;
959 }
960 }
961 }
962
963 let success_rate = successes as f64 / num_trials as f64;
964
965 if success_rate > 0.5 {
966 threshold = (threshold + max_noise) / 2.0;
967 } else {
968 threshold = (min_noise + threshold) / 2.0;
969 }
970
971 search_range /= 2.0;
972 }
973
974 self.stats.threshold_estimate = threshold;
975 Ok(threshold)
976 }
977
978 pub fn get_stats(&self) -> &LDPCStats {
980 &self.stats
981 }
982
983 pub fn reset_stats(&mut self) {
985 self.stats = LDPCStats::default();
986 }
987
988 pub fn get_parameters(&self) -> (usize, usize, usize) {
990 (self.config.n, self.config.k, self.config.m)
991 }
992
993 pub fn get_tanner_graph(&self) -> &TannerGraph {
995 &self.tanner_graph
996 }
997}
998
999pub fn benchmark_quantum_ldpc_codes() -> Result<HashMap<String, f64>> {
1001 let mut results = HashMap::new();
1002
1003 let configs = vec![
1005 LDPCConfig {
1006 k: 10,
1007 n: 20,
1008 m: 10,
1009 construction_method: LDPCConstructionMethod::RandomRegular,
1010 bp_algorithm: BeliefPropagationAlgorithm::SumProduct,
1011 ..Default::default()
1012 },
1013 LDPCConfig {
1014 k: 15,
1015 n: 30,
1016 m: 15,
1017 construction_method: LDPCConstructionMethod::ProgressiveEdgeGrowth,
1018 bp_algorithm: BeliefPropagationAlgorithm::MinSum,
1019 ..Default::default()
1020 },
1021 LDPCConfig {
1022 k: 20,
1023 n: 40,
1024 m: 20,
1025 construction_method: LDPCConstructionMethod::QuantumBicycle,
1026 bp_algorithm: BeliefPropagationAlgorithm::NormalizedMinSum,
1027 ..Default::default()
1028 },
1029 ];
1030
1031 for (i, config) in configs.into_iter().enumerate() {
1032 let start = std::time::Instant::now();
1033
1034 let mut ldpc_code = QuantumLDPCCode::new(config)?;
1035
1036 for _ in 0..50 {
1038 let errors: Vec<bool> = (0..ldpc_code.config.n)
1040 .map(|_| fastrand::f64() < 0.05)
1041 .collect();
1042
1043 let llrs: Vec<f64> = errors
1045 .iter()
1046 .map(|&error| if error { -1.0 } else { 1.0 })
1047 .collect();
1048
1049 let syndrome = ldpc_code.calculate_syndrome(&errors);
1051
1052 let _result = ldpc_code.decode_belief_propagation(&llrs, &syndrome)?;
1054 }
1055
1056 let time = start.elapsed().as_secs_f64() * 1000.0;
1057 results.insert(format!("config_{}", i), time);
1058
1059 let stats = ldpc_code.get_stats();
1061 results.insert(format!("config_{}_success_rate", i), stats.convergence_rate);
1062 results.insert(
1063 format!("config_{}_avg_iterations", i),
1064 stats.avg_bp_iterations,
1065 );
1066 }
1067
1068 Ok(results)
1069}
1070
1071#[cfg(test)]
1072mod tests {
1073 use super::*;
1074 use approx::assert_abs_diff_eq;
1075
1076 #[test]
1077 fn test_ldpc_code_creation() {
1078 let config = LDPCConfig::default();
1079 let ldpc_code = QuantumLDPCCode::new(config);
1080 assert!(ldpc_code.is_ok());
1081 }
1082
1083 #[test]
1084 fn test_tanner_graph_construction() {
1085 let config = LDPCConfig {
1086 k: 5,
1087 n: 10,
1088 m: 5,
1089 dv: 2,
1090 dc: 4,
1091 ..Default::default()
1092 };
1093
1094 let tanner_graph = QuantumLDPCCode::construct_random_regular(&config);
1095 assert!(tanner_graph.is_ok());
1096
1097 let graph = tanner_graph.unwrap();
1098 assert_eq!(graph.variable_nodes.len(), 10);
1099 assert_eq!(graph.check_nodes.len(), 5);
1100 }
1101
1102 #[test]
1103 fn test_variable_node_operations() {
1104 let mut var_node = VariableNode::new(0);
1105 var_node.channel_llr = 1.0;
1106 var_node.incoming_messages.insert(1, 0.5);
1107 var_node.incoming_messages.insert(2, -0.3);
1108
1109 var_node.update_belief();
1110 assert_abs_diff_eq!(var_node.belief, 1.2, epsilon = 1e-10);
1111
1112 let outgoing = var_node.compute_outgoing_message(1);
1113 assert_abs_diff_eq!(outgoing, 0.7, epsilon = 1e-10);
1114 }
1115
1116 #[test]
1117 fn test_check_node_operations() {
1118 let mut check_node = CheckNode::new(0);
1119 check_node.incoming_messages.insert(1, 0.8);
1120 check_node.incoming_messages.insert(2, -0.6);
1121 check_node.incoming_messages.insert(3, 1.2);
1122
1123 let sum_product_msg = check_node.compute_outgoing_message_sum_product(1);
1124 assert!(sum_product_msg.is_finite());
1125
1126 let min_sum_msg = check_node.compute_outgoing_message_min_sum(1);
1127 assert_abs_diff_eq!(min_sum_msg.abs(), 0.6, epsilon = 1e-10);
1128 }
1129
1130 #[test]
1131 fn test_belief_propagation_decoding() {
1132 let config = LDPCConfig {
1133 k: 3,
1134 n: 6,
1135 m: 3,
1136 max_bp_iterations: 10,
1137 ..Default::default()
1138 };
1139
1140 let mut ldpc_code = QuantumLDPCCode::new(config).unwrap();
1141
1142 let llrs = vec![1.0, -1.0, 1.0, 1.0, -1.0, 1.0];
1143 let syndrome = vec![false, true, false];
1144
1145 let result = ldpc_code.decode_belief_propagation(&llrs, &syndrome);
1146 assert!(result.is_ok());
1147
1148 let bp_result = result.unwrap();
1149 assert_eq!(bp_result.decoded_bits.len(), 6);
1150 assert!(bp_result.iterations <= 10);
1151 }
1152
1153 #[test]
1154 fn test_syndrome_calculation() {
1155 let config = LDPCConfig {
1156 k: 2,
1157 n: 4,
1158 m: 2,
1159 ..Default::default()
1160 };
1161
1162 let ldpc_code = QuantumLDPCCode::new(config).unwrap();
1163
1164 let codeword = vec![false, true, false, true];
1165 let syndrome = ldpc_code.calculate_syndrome(&codeword);
1166
1167 assert_eq!(syndrome.len(), 2);
1168 }
1169
1170 #[test]
1171 fn test_syndrome_circuit_generation() {
1172 let config = LDPCConfig {
1173 k: 3,
1174 n: 6,
1175 m: 3,
1176 ..Default::default()
1177 };
1178
1179 let ldpc_code = QuantumLDPCCode::new(config).unwrap();
1180 let circuit = ldpc_code.syndrome_circuit();
1181
1182 assert!(circuit.is_ok());
1183 let syndrome_circuit = circuit.unwrap();
1184 assert_eq!(syndrome_circuit.num_qubits, 9); }
1186
1187 #[test]
1188 fn test_different_construction_methods() {
1189 let base_config = LDPCConfig {
1190 k: 3,
1191 n: 6,
1192 m: 3,
1193 ..Default::default()
1194 };
1195
1196 let methods = vec![
1197 LDPCConstructionMethod::RandomRegular,
1198 LDPCConstructionMethod::ProgressiveEdgeGrowth,
1199 LDPCConstructionMethod::QuantumBicycle,
1200 LDPCConstructionMethod::SurfaceCode,
1201 ];
1202
1203 for method in methods {
1204 let mut config = base_config.clone();
1205 config.construction_method = method;
1206
1207 let ldpc_code = QuantumLDPCCode::new(config);
1208 assert!(ldpc_code.is_ok(), "Failed for method: {:?}", method);
1209 }
1210 }
1211
1212 #[test]
1213 fn test_different_bp_algorithms() {
1214 let base_config = LDPCConfig {
1215 k: 3,
1216 n: 6,
1217 m: 3,
1218 ..Default::default()
1219 };
1220
1221 let algorithms = vec![
1222 BeliefPropagationAlgorithm::SumProduct,
1223 BeliefPropagationAlgorithm::MinSum,
1224 BeliefPropagationAlgorithm::NormalizedMinSum,
1225 BeliefPropagationAlgorithm::OffsetMinSum,
1226 ];
1227
1228 for algorithm in algorithms {
1229 let mut config = base_config.clone();
1230 config.bp_algorithm = algorithm;
1231
1232 let mut ldpc_code = QuantumLDPCCode::new(config).unwrap();
1233
1234 let llrs = vec![1.0, -1.0, 1.0, 1.0, -1.0, 1.0];
1235 let syndrome = vec![false, true, false];
1236
1237 let result = ldpc_code.decode_belief_propagation(&llrs, &syndrome);
1238 assert!(result.is_ok(), "Failed for algorithm: {:?}", algorithm);
1239 }
1240 }
1241
1242 #[test]
1243 fn test_stats_updates() {
1244 let mut stats = LDPCStats::default();
1245
1246 stats.update_after_decoding(true, 5, 10.0);
1247 assert_eq!(stats.total_decodings, 1);
1248 assert_eq!(stats.successful_decodings, 1);
1249 assert_abs_diff_eq!(stats.avg_bp_iterations, 5.0, epsilon = 1e-10);
1250 assert_abs_diff_eq!(stats.block_error_rate, 0.0, epsilon = 1e-10);
1251
1252 stats.update_after_decoding(false, 8, 15.0);
1253 assert_eq!(stats.total_decodings, 2);
1254 assert_eq!(stats.successful_decodings, 1);
1255 assert_abs_diff_eq!(stats.avg_bp_iterations, 6.5, epsilon = 1e-10);
1256 assert_abs_diff_eq!(stats.block_error_rate, 0.5, epsilon = 1e-10);
1257 }
1258}