use crate::error::{QuantumError, Result};
use std::os::raw::c_char;
use moonlab_sys::{
surface_code_clifford_apply_error, surface_code_clifford_create,
surface_code_clifford_data_index, surface_code_clifford_free,
surface_code_clifford_measure_x_syndromes,
surface_code_clifford_measure_z_syndromes,
surface_code_clifford_syndrome_weight, surface_code_clifford_t,
};
use std::ptr;
pub struct SurfaceCode {
ptr: *mut surface_code_clifford_t,
distance: u32,
}
impl SurfaceCode {
pub fn new(distance: u32, rng_seed: u64) -> Result<Self> {
if distance < 3 || distance % 2 == 0 {
return Err(QuantumError::Ffi(format!(
"surface code distance must be odd and >= 3, got {distance}"
)));
}
let ptr = unsafe { surface_code_clifford_create(distance, rng_seed) };
if ptr.is_null() {
return Err(QuantumError::Ffi(
"surface_code_clifford_create returned NULL".to_string(),
));
}
Ok(Self { ptr, distance })
}
pub fn distance(&self) -> u32 {
self.distance
}
pub fn num_data_qubits(&self) -> u32 {
self.distance * self.distance
}
pub fn num_ancillas_per_sector(&self) -> u32 {
let m = self.distance - 1;
m * m
}
pub fn data_index(&self, row: u32, col: u32) -> Result<u32> {
if row >= self.distance || col >= self.distance {
return Err(QuantumError::InvalidQubit {
index: (row * self.distance + col) as usize,
max: (self.distance * self.distance) as usize,
});
}
Ok(unsafe { surface_code_clifford_data_index(self.ptr, row, col) })
}
pub fn apply_error(&mut self, q: u32, error_type: char) -> Result<()> {
if q >= self.num_data_qubits() {
return Err(QuantumError::InvalidQubit {
index: q as usize,
max: self.num_data_qubits() as usize,
});
}
let c = match error_type {
'X' | 'x' => b'X' as c_char,
'Y' | 'y' => b'Y' as c_char,
'Z' | 'z' => b'Z' as c_char,
_ => return Err(QuantumError::Ffi(format!(
"error_type must be one of X / Y / Z, got '{error_type}'"
))),
};
let rc = unsafe {
surface_code_clifford_apply_error(self.ptr, q, c)
};
if rc != 0 {
return Err(QuantumError::Ffi(format!(
"surface_code_clifford_apply_error rc={rc}"
)));
}
Ok(())
}
pub fn measure_z_syndromes(&mut self) -> Result<()> {
let rc = unsafe { surface_code_clifford_measure_z_syndromes(self.ptr) };
if rc != 0 {
return Err(QuantumError::Ffi(format!(
"measure_z_syndromes rc={rc}"
)));
}
Ok(())
}
pub fn measure_x_syndromes(&mut self) -> Result<()> {
let rc = unsafe { surface_code_clifford_measure_x_syndromes(self.ptr) };
if rc != 0 {
return Err(QuantumError::Ffi(format!(
"measure_x_syndromes rc={rc}"
)));
}
Ok(())
}
pub fn syndrome_weight(&self) -> u32 {
unsafe { surface_code_clifford_syndrome_weight(self.ptr) }
}
}
impl Drop for SurfaceCode {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe { surface_code_clifford_free(self.ptr) };
self.ptr = ptr::null_mut();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn distance_3_has_expected_layout() {
let code = SurfaceCode::new(3, 1).unwrap();
assert_eq!(code.distance(), 3);
assert_eq!(code.num_data_qubits(), 9);
assert_eq!(code.num_ancillas_per_sector(), 4);
}
#[test]
fn rejects_even_or_too_small_distance() {
assert!(SurfaceCode::new(2, 1).is_err());
assert!(SurfaceCode::new(4, 1).is_err());
assert!(SurfaceCode::new(1, 1).is_err());
}
#[test]
fn computational_basis_start_has_clean_z_syndromes() {
let mut code = SurfaceCode::new(3, 42).unwrap();
code.measure_z_syndromes().unwrap();
let w_z_only = code.syndrome_weight();
code.measure_z_syndromes().unwrap();
assert_eq!(code.syndrome_weight(), w_z_only,
"Z stabiliser measurement should be idempotent on |0...0>");
}
#[test]
fn x_error_lights_z_stabilisers() {
let mut code = SurfaceCode::new(3, 42).unwrap();
let q = code.data_index(1, 1).unwrap();
code.apply_error(q, 'X').unwrap();
code.measure_z_syndromes().unwrap();
assert!(
code.syndrome_weight() > 0,
"Z-type stabilisers should detect an X error at the centre"
);
}
#[test]
fn z_error_lights_x_stabilisers() {
let mut code = SurfaceCode::new(3, 7).unwrap();
let q = code.data_index(1, 1).unwrap();
code.apply_error(q, 'Z').unwrap();
code.measure_x_syndromes().unwrap();
assert!(
code.syndrome_weight() > 0,
"X-type stabilisers should detect a Z error at the centre"
);
}
#[test]
fn rejects_unknown_error_type() {
let mut code = SurfaceCode::new(3, 1).unwrap();
let q = code.data_index(0, 0).unwrap();
assert!(code.apply_error(q, 'W').is_err());
}
#[test]
fn rejects_out_of_range_qubit() {
let mut code = SurfaceCode::new(3, 1).unwrap();
assert!(code.apply_error(100, 'X').is_err());
assert!(code.data_index(10, 0).is_err());
}
}