use crate::error::{QuantumError, Result};
use moonlab_sys::{
clifford_cnot, clifford_cz, clifford_h, clifford_measure,
clifford_num_qubits, clifford_s, clifford_s_dag, clifford_sample_all,
clifford_swap, clifford_tableau_create, clifford_tableau_free,
clifford_tableau_t, clifford_x, clifford_y, clifford_z,
};
use std::ptr;
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct MeasureResult {
pub outcome: u8,
pub deterministic: bool,
}
pub struct CliffordTableau {
handle: *mut clifford_tableau_t,
num_qubits: usize,
rng_state: u64,
}
unsafe impl Send for CliffordTableau {}
impl CliffordTableau {
pub fn new(num_qubits: usize) -> Result<Self> {
if num_qubits == 0 {
return Err(QuantumError::InvalidQubit { index: 0, max: 1 });
}
let handle = unsafe { clifford_tableau_create(num_qubits) };
if handle.is_null() {
return Err(QuantumError::AllocationFailed(num_qubits));
}
let seed = (handle as usize as u64).wrapping_mul(0x9e3779b97f4a7c15) | 1;
Ok(Self {
handle,
num_qubits,
rng_state: seed,
})
}
pub fn num_qubits(&self) -> usize {
unsafe { clifford_num_qubits(self.handle) }
}
pub fn set_rng_seed(&mut self, seed: u64) -> &mut Self {
self.rng_state = if seed == 0 { 1 } else { seed };
self
}
pub fn h(&mut self, q: usize) -> Result<&mut Self> {
self.check_qubit(q)?;
let rc = unsafe { clifford_h(self.handle, q) };
self.guard_rc(rc, "clifford_h")
}
pub fn s(&mut self, q: usize) -> Result<&mut Self> {
self.check_qubit(q)?;
let rc = unsafe { clifford_s(self.handle, q) };
self.guard_rc(rc, "clifford_s")
}
pub fn sdag(&mut self, q: usize) -> Result<&mut Self> {
self.check_qubit(q)?;
let rc = unsafe { clifford_s_dag(self.handle, q) };
self.guard_rc(rc, "clifford_s_dag")
}
pub fn x(&mut self, q: usize) -> Result<&mut Self> {
self.check_qubit(q)?;
let rc = unsafe { clifford_x(self.handle, q) };
self.guard_rc(rc, "clifford_x")
}
pub fn y(&mut self, q: usize) -> Result<&mut Self> {
self.check_qubit(q)?;
let rc = unsafe { clifford_y(self.handle, q) };
self.guard_rc(rc, "clifford_y")
}
pub fn z(&mut self, q: usize) -> Result<&mut Self> {
self.check_qubit(q)?;
let rc = unsafe { clifford_z(self.handle, q) };
self.guard_rc(rc, "clifford_z")
}
pub fn cnot(&mut self, control: usize, target: usize) -> Result<&mut Self> {
self.check_qubit(control)?;
self.check_qubit(target)?;
let rc = unsafe { clifford_cnot(self.handle, control, target) };
self.guard_rc(rc, "clifford_cnot")
}
pub fn cz(&mut self, a: usize, b: usize) -> Result<&mut Self> {
self.check_qubit(a)?;
self.check_qubit(b)?;
let rc = unsafe { clifford_cz(self.handle, a, b) };
self.guard_rc(rc, "clifford_cz")
}
pub fn swap(&mut self, a: usize, b: usize) -> Result<&mut Self> {
self.check_qubit(a)?;
self.check_qubit(b)?;
let rc = unsafe { clifford_swap(self.handle, a, b) };
self.guard_rc(rc, "clifford_swap")
}
pub fn measure(&mut self, q: usize) -> Result<MeasureResult> {
self.check_qubit(q)?;
let mut rng = self.rng_state;
let mut outcome: std::os::raw::c_int = 0;
let mut kind: std::os::raw::c_int = 0;
let rc = unsafe {
clifford_measure(self.handle, q, &mut rng, &mut outcome, &mut kind)
};
if rc != 0 {
return Err(QuantumError::Ffi(format!("clifford_measure rc={rc}")));
}
self.rng_state = rng;
Ok(MeasureResult {
outcome: if outcome == 0 { 0 } else { 1 },
deterministic: kind == 0,
})
}
pub fn sample_all(&mut self) -> Result<u64> {
if self.num_qubits > 64 {
return Err(QuantumError::UnsupportedOperation(format!(
"sample_all() supports up to 64 qubits; got {}. \
Use measure(q) in a loop for wider tableaus.",
self.num_qubits
)));
}
let mut rng = self.rng_state;
let mut result: u64 = 0;
let rc =
unsafe { clifford_sample_all(self.handle, &mut rng, &mut result) };
if rc != 0 {
return Err(QuantumError::Ffi(format!(
"clifford_sample_all rc={rc}"
)));
}
self.rng_state = rng;
Ok(result)
}
fn check_qubit(&self, q: usize) -> Result<()> {
if q >= self.num_qubits {
return Err(QuantumError::InvalidQubit {
index: q,
max: self.num_qubits,
});
}
Ok(())
}
fn guard_rc(&mut self, rc: i32, name: &'static str) -> Result<&mut Self> {
if rc != 0 {
Err(QuantumError::Ffi(format!("{name} rc={rc}")))
} else {
Ok(self)
}
}
}
impl Drop for CliffordTableau {
fn drop(&mut self) {
if !self.handle.is_null() {
unsafe { clifford_tableau_free(self.handle) };
self.handle = ptr::null_mut();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_and_num_qubits() {
let c = CliffordTableau::new(8).unwrap();
assert_eq!(c.num_qubits(), 8);
}
#[test]
fn reject_zero_qubits() {
assert!(CliffordTableau::new(0).is_err());
}
#[test]
fn ground_state_measurements_are_deterministic_zero() {
let mut c = CliffordTableau::new(4).unwrap();
for q in 0..4 {
let r = c.measure(q).unwrap();
assert_eq!(r.outcome, 0, "qubit {q}");
assert!(r.deterministic, "qubit {q} should be deterministic");
}
}
#[test]
fn ghz_sample_collapses_to_aligned_string() {
let n: usize = 6;
let mut c = CliffordTableau::new(n).unwrap();
c.h(0).unwrap();
for q in 1..n {
c.cnot(0, q).unwrap();
}
let bits = c.sample_all().unwrap();
let mask = (1u64 << n) - 1;
assert!(
bits == 0 || bits == mask,
"GHZ sample = 0x{bits:x} not in {{0, 0x{mask:x}}}"
);
}
#[test]
fn measure_advances_rng_state_on_random_branch() {
let mut a = CliffordTableau::new(1).unwrap();
a.set_rng_seed(0xdeadbeefcafebabe);
a.h(0).unwrap();
let ra = a.measure(0).unwrap();
assert!(!ra.deterministic);
let mut b = CliffordTableau::new(1).unwrap();
b.set_rng_seed(0xdeadbeefcafebabe);
b.h(0).unwrap();
let rb = b.measure(0).unwrap();
assert_eq!(ra.outcome, rb.outcome);
}
#[test]
fn cnot_index_range_check() {
let mut c = CliffordTableau::new(2).unwrap();
assert!(c.cnot(0, 5).is_err());
assert!(c.cnot(5, 0).is_err());
}
}