use crate::error::{QuantumError, Result};
use moonlab_sys::{
moonlab_decoder_code_t, moonlab_decoder_decode, moonlab_decoder_decode_by_name,
moonlab_decoder_input_t, moonlab_decoder_slot_available,
moonlab_decoder_slot_name, moonlab_list_decoders, moonlab_lookup_decoder,
moonlab_num_decoders, moonlab_register_decoder, moonlab_unregister_decoder,
};
use std::ffi::{c_void, CStr, CString};
use std::os::raw::c_char;
use std::ptr;
use std::sync::Mutex;
#[repr(u32)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum DecoderSlot {
Greedy = 0,
MwpmExact = 1,
Sbnn = 2,
LibirrepSs = 3,
Pymatching = 4,
}
pub const MOONLAB_DECODER_OK: i32 = 0;
pub const MOONLAB_DECODER_NOT_BUILT: i32 = -401;
pub const MOONLAB_DECODER_BAD_ARG: i32 = -402;
pub const MOONLAB_DECODER_INFEASIBLE: i32 = -403;
pub const MOONLAB_DECODER_OOM: i32 = -404;
#[derive(Debug, Clone)]
pub struct CodeGeometry {
pub distance: i32,
pub num_qubits: i32,
pub is_toric: bool,
}
pub fn slot_available(slot: DecoderSlot) -> bool {
unsafe { moonlab_decoder_slot_available(slot as u32) == 1 }
}
pub fn slot_name(slot: DecoderSlot) -> &'static str {
unsafe {
let p = moonlab_decoder_slot_name(slot as u32);
if p.is_null() { "unknown" } else {
CStr::from_ptr(p).to_str().unwrap_or("unknown")
}
}
}
pub fn decode(slot: DecoderSlot,
code: &CodeGeometry,
syndromes: &[u8],
rng_seed: u64) -> Result<Vec<u8>> {
let c_code = moonlab_decoder_code_t {
distance: code.distance,
num_qubits: code.num_qubits,
is_toric: if code.is_toric { 1 } else { 0 },
};
let mut corrections = vec![0u8; code.num_qubits as usize];
let input = moonlab_decoder_input_t {
code: &c_code as *const _,
syndromes: syndromes.as_ptr(),
corrections: corrections.as_mut_ptr(),
num_stabilisers: syndromes.len() as i32,
rng_seed,
};
let rc = unsafe { moonlab_decoder_decode(slot as u32, &input) };
if rc == MOONLAB_DECODER_OK {
Ok(corrections)
} else if rc == MOONLAB_DECODER_NOT_BUILT {
Err(QuantumError::Ffi(format!(
"decoder slot {slot:?} not built (rebuild with QSIM_ENABLE_*)"
)))
} else {
Err(QuantumError::Ffi(format!("decode({slot:?}): rc={rc}")))
}
}
pub fn decode_by_name(name: &str,
code: &CodeGeometry,
syndromes: &[u8],
rng_seed: u64) -> Result<Vec<u8>> {
let c_code = moonlab_decoder_code_t {
distance: code.distance,
num_qubits: code.num_qubits,
is_toric: if code.is_toric { 1 } else { 0 },
};
let mut corrections = vec![0u8; code.num_qubits as usize];
let input = moonlab_decoder_input_t {
code: &c_code as *const _,
syndromes: syndromes.as_ptr(),
corrections: corrections.as_mut_ptr(),
num_stabilisers: syndromes.len() as i32,
rng_seed,
};
let c_name = CString::new(name)
.map_err(|e| QuantumError::Ffi(format!("bad decoder name: {e}")))?;
let rc = unsafe {
moonlab_decoder_decode_by_name(c_name.as_ptr(), &input)
};
if rc == MOONLAB_DECODER_OK {
Ok(corrections)
} else {
Err(QuantumError::Ffi(format!("decode_by_name({name:?}): rc={rc}")))
}
}
pub fn num_decoders() -> i32 {
unsafe { moonlab_num_decoders() }
}
pub fn list_decoders() -> Vec<String> {
let n = num_decoders();
if n <= 0 { return Vec::new(); }
let mut buf: Vec<*const c_char> = vec![ptr::null(); n as usize];
let written = unsafe {
moonlab_list_decoders(buf.as_mut_ptr(), n) as usize
};
buf.into_iter().take(written).filter_map(|p| {
if p.is_null() { None } else {
unsafe { CStr::from_ptr(p).to_str().ok().map(|s| s.to_owned()) }
}
}).collect()
}
pub fn lookup_decoder(name: &str) -> Option<String> {
let c_name = CString::new(name).ok()?;
let p = unsafe { moonlab_lookup_decoder(c_name.as_ptr()) };
if p.is_null() { return None; }
unsafe {
let entry = &*p;
if entry.description.is_null() { Some(String::new()) }
else { CStr::from_ptr(entry.description).to_str().ok().map(|s| s.to_owned()) }
}
}
pub type DecoderClosure =
Box<dyn Fn(&CodeGeometry, &[u8], u64) -> Result<Vec<u8>> + Send + Sync>;
struct RegistrySlot {
closure: Box<DecoderClosure>,
}
static REGISTERED_DECODERS: Mutex<Vec<(String, Box<RegistrySlot>)>> =
Mutex::new(Vec::new());
extern "C" fn rust_decoder_trampoline(
input: *const moonlab_decoder_input_t,
ctx: *mut c_void,
) -> i32 {
if input.is_null() || ctx.is_null() { return MOONLAB_DECODER_BAD_ARG; }
let slot: &RegistrySlot = unsafe { &*(ctx as *const RegistrySlot) };
let in_ref = unsafe { &*input };
let code_ref = unsafe { &*in_ref.code };
let geom = CodeGeometry {
distance: code_ref.distance,
num_qubits: code_ref.num_qubits,
is_toric: code_ref.is_toric != 0,
};
let syndromes = unsafe {
std::slice::from_raw_parts(in_ref.syndromes, in_ref.num_stabilisers as usize)
};
let result = (slot.closure)(&geom, syndromes, in_ref.rng_seed);
match result {
Ok(corr) => {
let n_q = code_ref.num_qubits as usize;
if corr.len() < n_q { return MOONLAB_DECODER_OOM; }
for i in 0..n_q {
unsafe { *in_ref.corrections.add(i) = corr[i]; }
}
MOONLAB_DECODER_OK
}
Err(_) => MOONLAB_DECODER_OOM,
}
}
pub fn register_decoder<F>(name: &str,
description: &str,
f: F) -> Result<()>
where
F: Fn(&CodeGeometry, &[u8], u64) -> Result<Vec<u8>> + Send + Sync + 'static,
{
let c_name = CString::new(name)
.map_err(|e| QuantumError::Ffi(format!("bad decoder name: {e}")))?;
let c_desc = if description.is_empty() { None } else {
Some(CString::new(description)
.map_err(|e| QuantumError::Ffi(format!("bad description: {e}")))?)
};
let slot = Box::new(RegistrySlot {
closure: Box::new(Box::new(f) as DecoderClosure),
});
let ctx_ptr: *mut c_void = (&*slot) as *const RegistrySlot as *mut c_void;
let rc = unsafe {
moonlab_register_decoder(
c_name.as_ptr(),
Some(rust_decoder_trampoline),
ctx_ptr,
c_desc.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()),
)
};
if rc != MOONLAB_DECODER_OK {
return Err(QuantumError::Ffi(format!("register_decoder({name:?}): rc={rc}")));
}
REGISTERED_DECODERS.lock().unwrap().push((name.to_owned(), slot));
Ok(())
}
pub fn unregister_decoder(name: &str) -> Result<()> {
let c_name = CString::new(name)
.map_err(|e| QuantumError::Ffi(format!("bad decoder name: {e}")))?;
let rc = unsafe { moonlab_unregister_decoder(c_name.as_ptr()) };
if rc != MOONLAB_DECODER_OK {
return Err(QuantumError::Ffi(format!("unregister_decoder({name:?}): rc={rc}")));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slot_names_roundtrip() {
assert_eq!(slot_name(DecoderSlot::Greedy), "greedy");
assert_eq!(slot_name(DecoderSlot::MwpmExact), "mwpm_exact");
assert_eq!(slot_name(DecoderSlot::Sbnn), "sbnn");
}
#[test]
fn slot_availability() {
assert!(slot_available(DecoderSlot::Greedy));
assert!(slot_available(DecoderSlot::MwpmExact));
assert!(!slot_available(DecoderSlot::Sbnn));
let _ = slot_available(DecoderSlot::Pymatching);
let _ = slot_available(DecoderSlot::LibirrepSs);
}
#[test]
fn greedy_zero_syndrome() {
let d = 3;
let s = vec![0u8; d * d];
let corr = decode(
DecoderSlot::Greedy,
&CodeGeometry { distance: d as i32, num_qubits: (2 * d * d) as i32, is_toric: true },
&s, 0,
).unwrap();
assert_eq!(corr.iter().map(|&b| b as i32).sum::<i32>(), 0);
}
#[test]
fn mwpm_exact_l1_4() {
let d = 5usize;
let mut s = vec![0u8; d * d];
s[0] = 1;
s[12] = 1;
let corr = decode(
DecoderSlot::MwpmExact,
&CodeGeometry { distance: d as i32, num_qubits: (2 * d * d) as i32, is_toric: true },
&s, 0,
).unwrap();
assert_eq!(corr.iter().map(|&b| b as i32).sum::<i32>(), 4);
}
#[test]
fn sbnn_not_built() {
let d = 3;
let s = vec![0u8; d * d];
let r = decode(
DecoderSlot::Sbnn,
&CodeGeometry { distance: d as i32, num_qubits: (2 * d * d) as i32, is_toric: true },
&s, 0,
);
assert!(r.is_err());
}
#[test]
fn registry_lists_builtins() {
let names = list_decoders();
assert!(names.contains(&"greedy".to_string()),
"greedy missing from registry; got {names:?}");
assert!(names.contains(&"mwpm_exact".to_string()));
assert!(names.contains(&"sbnn".to_string()));
assert!(names.contains(&"libirrep_single_shot".to_string()));
assert!(names.contains(&"pymatching".to_string()));
}
#[test]
fn registry_lookup_unknown_returns_none() {
assert!(lookup_decoder("definitely-not-a-real-decoder").is_none());
}
#[test]
fn registry_register_dispatch_unregister() {
register_decoder(
"rust-sentinel-test",
"round-trip sanity decoder",
|geom, _syndromes, _seed| {
let mut out = vec![0u8; geom.num_qubits as usize];
out[0] = 0xC3;
Ok(out)
},
).unwrap();
assert!(list_decoders().contains(&"rust-sentinel-test".to_string()));
let desc = lookup_decoder("rust-sentinel-test").unwrap();
assert!(desc.contains("round-trip"));
let d = 3;
let corr = decode_by_name(
"rust-sentinel-test",
&CodeGeometry { distance: d as i32, num_qubits: (2 * d * d) as i32, is_toric: true },
&vec![0u8; d * d], 0,
).unwrap();
assert_eq!(corr[0], 0xC3);
assert!(corr[1..].iter().all(|&b| b == 0));
unregister_decoder("rust-sentinel-test").unwrap();
assert!(!list_decoders().contains(&"rust-sentinel-test".to_string()));
}
#[test]
fn registry_unregister_unknown_errs() {
assert!(unregister_decoder("never-registered").is_err());
}
}