use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use ruqu_core::gate::Gate;
use ruqu_core::state::QuantumState;
use ruqu_core::types::QubitIndex;
pub struct SurfaceCodeConfig {
pub distance: u32,
pub num_cycles: u32,
pub noise_rate: f64,
pub seed: Option<u64>,
}
pub struct SurfaceCodeResult {
pub logical_errors: u32,
pub total_cycles: u32,
pub logical_error_rate: f64,
pub syndrome_history: Vec<Vec<bool>>,
}
pub struct SurfaceCodeLayout {
pub data_qubits: Vec<QubitIndex>,
pub x_ancillas: Vec<QubitIndex>,
pub z_ancillas: Vec<QubitIndex>,
pub x_stabilizers: Vec<Vec<QubitIndex>>,
pub z_stabilizers: Vec<Vec<QubitIndex>>,
}
impl SurfaceCodeLayout {
pub fn distance_3() -> Self {
Self {
data_qubits: (0..9).collect(),
x_ancillas: vec![9, 10, 11, 12],
z_ancillas: vec![13, 14, 15, 16],
x_stabilizers: vec![
vec![0, 1, 3, 4], vec![1, 2, 4, 5], vec![3, 4, 6, 7], vec![4, 5, 7, 8], ],
z_stabilizers: vec![
vec![0, 1], vec![2, 5], vec![3, 6], vec![7, 8], ],
}
}
pub fn total_qubits(&self) -> u32 {
(self.data_qubits.len() + self.x_ancillas.len() + self.z_ancillas.len()) as u32
}
pub fn num_stabilizers(&self) -> usize {
self.x_stabilizers.len() + self.z_stabilizers.len()
}
}
fn inject_noise(
state: &mut QuantumState,
data_qubits: &[QubitIndex],
noise_rate: f64,
rng: &mut StdRng,
) -> ruqu_core::error::Result<()> {
for &q in data_qubits {
let r: f64 = rng.gen();
if r < noise_rate {
state.apply_gate(&Gate::X(q))?;
} else if r < 2.0 * noise_rate {
state.apply_gate(&Gate::Z(q))?;
}
}
Ok(())
}
fn run_cycle(
state: &mut QuantumState,
layout: &SurfaceCodeLayout,
) -> ruqu_core::error::Result<Vec<bool>> {
for &a in layout.x_ancillas.iter().chain(layout.z_ancillas.iter()) {
state.reset_qubit(a)?;
}
for (i, stabilizer) in layout.x_stabilizers.iter().enumerate() {
let ancilla = layout.x_ancillas[i];
state.apply_gate(&Gate::H(ancilla))?;
for &data in stabilizer {
state.apply_gate(&Gate::CNOT(ancilla, data))?;
}
state.apply_gate(&Gate::H(ancilla))?;
}
for (i, stabilizer) in layout.z_stabilizers.iter().enumerate() {
let ancilla = layout.z_ancillas[i];
for &data in stabilizer {
state.apply_gate(&Gate::CNOT(data, ancilla))?;
}
}
let mut syndrome = Vec::with_capacity(layout.num_stabilizers());
for &a in layout.x_ancillas.iter().chain(layout.z_ancillas.iter()) {
let outcome = state.measure(a)?;
syndrome.push(outcome.result);
}
Ok(syndrome)
}
fn decode_syndrome(syndrome: &[bool], layout: &SurfaceCodeLayout) -> Vec<Gate> {
let mut corrections = Vec::new();
let n_x = layout.x_stabilizers.len();
let x_syndrome = &syndrome[..n_x];
let x_triggered: Vec<usize> = x_syndrome
.iter()
.enumerate()
.filter(|(_, &s)| s)
.map(|(i, _)| i)
.collect();
if x_triggered.len() == 1 {
let data_q = layout.x_stabilizers[x_triggered[0]][0];
corrections.push(Gate::Z(data_q));
} else if x_triggered.len() >= 2 {
if let Some(q) = most_common_data_qubit(&layout.x_stabilizers, &x_triggered) {
corrections.push(Gate::Z(q));
}
}
let z_syndrome = &syndrome[n_x..];
let z_triggered: Vec<usize> = z_syndrome
.iter()
.enumerate()
.filter(|(_, &s)| s)
.map(|(i, _)| i)
.collect();
if z_triggered.len() == 1 {
let data_q = layout.z_stabilizers[z_triggered[0]][0];
corrections.push(Gate::X(data_q));
} else if z_triggered.len() >= 2 {
if let Some(q) = most_common_data_qubit(&layout.z_stabilizers, &z_triggered) {
corrections.push(Gate::X(q));
}
}
corrections
}
fn most_common_data_qubit(
stabilizers: &[Vec<QubitIndex>],
triggered_indices: &[usize],
) -> Option<QubitIndex> {
let mut counts: std::collections::HashMap<QubitIndex, usize> = std::collections::HashMap::new();
for &idx in triggered_indices {
for &dq in &stabilizers[idx] {
*counts.entry(dq).or_insert(0) += 1;
}
}
counts
.into_iter()
.max_by_key(|&(_, count)| count)
.map(|(qubit, _)| qubit)
}
pub fn run_surface_code(
config: &SurfaceCodeConfig,
) -> ruqu_core::error::Result<SurfaceCodeResult> {
assert_eq!(
config.distance, 3,
"Only distance-3 surface codes are currently supported"
);
let layout = SurfaceCodeLayout::distance_3();
let total_qubits = layout.total_qubits();
let mut state = match config.seed {
Some(s) => QuantumState::new_with_seed(total_qubits, s)?,
None => QuantumState::new(total_qubits)?,
};
let mut rng = match config.seed {
Some(s) => StdRng::seed_from_u64(s),
None => StdRng::from_entropy(),
};
let mut logical_errors = 0u32;
let mut syndrome_history = Vec::with_capacity(config.num_cycles as usize);
let logical_row: [QubitIndex; 3] = [0, 1, 2];
for _cycle in 0..config.num_cycles {
inject_noise(&mut state, &layout.data_qubits, config.noise_rate, &mut rng)?;
let syndrome = run_cycle(&mut state, &layout)?;
syndrome_history.push(syndrome.clone());
let corrections = decode_syndrome(&syndrome, &layout);
for gate in &corrections {
state.apply_gate(gate)?;
}
let mut row_parity = 1.0_f64;
for &q in &logical_row {
let z_exp = state.expectation_value(
&ruqu_core::types::PauliString {
ops: vec![(q, ruqu_core::types::PauliOp::Z)],
},
);
row_parity *= z_exp;
}
if row_parity < 0.0 {
logical_errors += 1;
}
}
let logical_error_rate = if config.num_cycles > 0 {
logical_errors as f64 / config.num_cycles as f64
} else {
0.0
};
Ok(SurfaceCodeResult {
logical_errors,
total_cycles: config.num_cycles,
logical_error_rate,
syndrome_history,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_layout_distance_3() {
let layout = SurfaceCodeLayout::distance_3();
assert_eq!(layout.data_qubits.len(), 9);
assert_eq!(layout.x_ancillas.len(), 4);
assert_eq!(layout.z_ancillas.len(), 4);
assert_eq!(layout.total_qubits(), 17);
assert_eq!(layout.num_stabilizers(), 8);
}
#[test]
fn test_x_stabilizers_cover_all_data() {
let layout = SurfaceCodeLayout::distance_3();
let mut covered: std::collections::HashSet<QubitIndex> = std::collections::HashSet::new();
for stab in &layout.x_stabilizers {
for &q in stab {
covered.insert(q);
}
}
for q in 0..9u32 {
assert!(covered.contains(&q), "data qubit {} not covered by X stabilizers", q);
}
}
#[test]
fn test_z_stabilizers_boundary() {
let layout = SurfaceCodeLayout::distance_3();
for stab in &layout.z_stabilizers {
assert_eq!(stab.len(), 2, "Z stabilizer should have weight 2");
}
}
#[test]
fn test_decode_syndrome_no_error() {
let layout = SurfaceCodeLayout::distance_3();
let syndrome = vec![false; 8];
let corrections = decode_syndrome(&syndrome, &layout);
assert!(corrections.is_empty(), "no corrections when syndrome is trivial");
}
#[test]
fn test_decode_syndrome_single_x_stabilizer() {
let layout = SurfaceCodeLayout::distance_3();
let mut syndrome = vec![false; 8];
syndrome[0] = true;
let corrections = decode_syndrome(&syndrome, &layout);
assert_eq!(corrections.len(), 1);
}
#[test]
fn test_decode_syndrome_single_z_stabilizer() {
let layout = SurfaceCodeLayout::distance_3();
let mut syndrome = vec![false; 8];
syndrome[4] = true;
let corrections = decode_syndrome(&syndrome, &layout);
assert_eq!(corrections.len(), 1);
}
#[test]
fn test_most_common_data_qubit() {
let stabilizers = vec![
vec![0, 1, 3, 4],
vec![1, 2, 4, 5],
];
let result = most_common_data_qubit(&stabilizers, &[0, 1]);
assert!(result == Some(1) || result == Some(4));
}
#[test]
#[should_panic(expected = "Only distance-3")]
fn test_unsupported_distance() {
let config = SurfaceCodeConfig {
distance: 5,
num_cycles: 1,
noise_rate: 0.01,
seed: Some(42),
};
let _ = run_surface_code(&config);
}
}