use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum QecCode {
SurfaceCode { distance: u32 },
SteaneCode,
ShorCode,
RepetitionCode { distance: u32 },
LdpcCode { n: u32, k: u32 },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct QecAnalysis {
pub code: QecCode,
pub logical_error_rate: f64,
pub physical_error_rate: f64,
pub overhead_qubits: u32,
pub syndrome_depth: u32,
pub logical_qubits: u32,
pub physical_qubits: u32,
}
impl QecCode {
pub fn physical_per_logical(&self) -> u32 {
match self {
Self::SurfaceCode { distance } => distance * distance,
Self::SteaneCode => 7,
Self::ShorCode => 9,
Self::RepetitionCode { distance } => *distance,
Self::LdpcCode { n, k } => {
if *k == 0 { return *n; }
n / k
}
}
}
pub fn code_distance(&self) -> u32 {
match self {
Self::SurfaceCode { distance } => *distance,
Self::SteaneCode => 3,
Self::ShorCode => 3,
Self::RepetitionCode { distance } => *distance,
Self::LdpcCode { n, .. } => {
(*n as f64).sqrt().ceil() as u32
}
}
}
pub fn syndrome_circuit_depth(&self) -> u32 {
match self {
Self::SurfaceCode { distance } => *distance,
Self::SteaneCode => 4,
Self::ShorCode => 8,
Self::RepetitionCode { .. } => 2,
Self::LdpcCode { .. } => 6,
}
}
}
impl QecAnalysis {
pub fn analyse(
num_logical_qubits: u32,
circuit_depth: u32,
code: QecCode,
phys_error_rate: f64,
) -> Self {
let d = code.code_distance();
let physical_per_logical = code.physical_per_logical();
let physical_qubits = num_logical_qubits * physical_per_logical;
let p_threshold = match &code {
QecCode::SurfaceCode { .. } => 0.01,
QecCode::SteaneCode => 0.005,
QecCode::ShorCode => 0.003,
QecCode::RepetitionCode { .. } => 0.03,
QecCode::LdpcCode { .. } => 0.008,
};
let ratio = phys_error_rate / p_threshold;
let exponent = (d as f64 + 1.0) / 2.0;
let a_prefactor = 0.1; let logical_error_per_round = a_prefactor * ratio.powf(exponent);
let total_rounds = circuit_depth;
let logical_error_rate =
1.0 - (1.0 - logical_error_per_round).powi(total_rounds as i32);
let syndrome_depth = code.syndrome_circuit_depth() * total_rounds;
Self {
code,
logical_error_rate,
physical_error_rate: phys_error_rate,
overhead_qubits: physical_per_logical,
syndrome_depth,
logical_qubits: num_logical_qubits,
physical_qubits,
}
}
pub fn meets_target(&self, target_error_rate: f64) -> bool {
self.logical_error_rate <= target_error_rate
}
pub fn suggest_distance(
phys_error_rate: f64,
target_logical_rate: f64,
circuit_depth: u32,
) -> u32 {
let p_threshold = 0.01; let ratio = phys_error_rate / p_threshold;
if ratio >= 1.0 {
return 0;
}
for d in (3..=49).step_by(2) {
let exponent = (d as f64 + 1.0) / 2.0;
let per_round = 0.1 * ratio.powf(exponent);
let total = 1.0 - (1.0 - per_round).powi(circuit_depth as i32);
if total <= target_logical_rate {
return d;
}
}
51 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_surface_code_overhead() {
let code = QecCode::SurfaceCode { distance: 5 };
assert_eq!(code.physical_per_logical(), 25);
assert_eq!(code.code_distance(), 5);
}
#[test]
fn test_steane_code() {
let code = QecCode::SteaneCode;
assert_eq!(code.physical_per_logical(), 7);
assert_eq!(code.code_distance(), 3);
}
#[test]
fn test_shor_code() {
let code = QecCode::ShorCode;
assert_eq!(code.physical_per_logical(), 9);
assert_eq!(code.code_distance(), 3);
}
#[test]
fn test_qec_analysis_below_threshold() {
let analysis = QecAnalysis::analyse(
10, 100, QecCode::SurfaceCode { distance: 7 },
0.001, );
assert!(analysis.logical_error_rate < 1.0);
assert!(analysis.logical_error_rate >= 0.0);
assert_eq!(analysis.physical_qubits, 10 * 49);
}
#[test]
fn test_qec_meets_target() {
let analysis = QecAnalysis::analyse(
5, 50,
QecCode::SurfaceCode { distance: 11 },
0.001,
);
assert!(analysis.meets_target(0.01));
}
#[test]
fn test_suggest_distance() {
let d = QecAnalysis::suggest_distance(0.001, 1e-6, 100);
assert!(d >= 3);
assert!(d <= 49);
}
#[test]
fn test_repetition_code() {
let code = QecCode::RepetitionCode { distance: 5 };
assert_eq!(code.physical_per_logical(), 5);
assert_eq!(code.code_distance(), 5);
}
}