use crate::error::{Result, SimulatorError};
use crate::stim_dem::DetectorErrorModel;
use crate::stim_executor::{ExecutionResult, StimExecutor};
use crate::stim_parser::StimCircuit;
use scirs2_core::random::prelude::*;
#[derive(Debug, Clone)]
pub struct CompiledStimCircuit {
circuit: StimCircuit,
pub num_qubits: usize,
pub num_measurements: usize,
pub num_detectors: usize,
pub num_observables: usize,
dem: Option<DetectorErrorModel>,
}
impl CompiledStimCircuit {
pub fn compile(circuit: &StimCircuit) -> Result<Self> {
let mut executor = StimExecutor::from_circuit(circuit);
let result = executor.execute(circuit)?;
Ok(Self {
circuit: circuit.clone(),
num_qubits: circuit.num_qubits,
num_measurements: result.num_measurements,
num_detectors: result.num_detectors,
num_observables: result.num_observables,
dem: None,
})
}
pub fn compile_with_dem(circuit: &StimCircuit) -> Result<Self> {
let mut compiled = Self::compile(circuit)?;
compiled.dem = Some(DetectorErrorModel::from_circuit(circuit)?);
Ok(compiled)
}
#[must_use]
pub fn circuit(&self) -> &StimCircuit {
&self.circuit
}
#[must_use]
pub fn has_dem(&self) -> bool {
self.dem.is_some()
}
}
#[derive(Debug)]
pub struct DetectorSampler {
compiled: CompiledStimCircuit,
}
impl DetectorSampler {
#[must_use]
pub fn new(compiled: CompiledStimCircuit) -> Self {
Self { compiled }
}
pub fn compile(circuit: &StimCircuit) -> Result<Self> {
Ok(Self::new(CompiledStimCircuit::compile(circuit)?))
}
pub fn compile_with_dem(circuit: &StimCircuit) -> Result<Self> {
Ok(Self::new(CompiledStimCircuit::compile_with_dem(circuit)?))
}
pub fn sample(&self) -> Result<ExecutionResult> {
let mut executor = StimExecutor::from_circuit(&self.compiled.circuit);
executor.execute(&self.compiled.circuit)
}
pub fn sample_detectors(&self) -> Result<Vec<bool>> {
let result = self.sample()?;
Ok(result.detector_values)
}
pub fn sample_measurements(&self) -> Result<Vec<bool>> {
let result = self.sample()?;
Ok(result.measurement_record)
}
pub fn sample_batch(&self, num_shots: usize) -> Result<Vec<ExecutionResult>> {
(0..num_shots).map(|_| self.sample()).collect()
}
pub fn sample_batch_detectors(&self, num_shots: usize) -> Result<Vec<Vec<bool>>> {
(0..num_shots).map(|_| self.sample_detectors()).collect()
}
pub fn sample_batch_detectors_packed(&self, num_shots: usize) -> Result<Vec<Vec<u8>>> {
let samples = self.sample_batch_detectors(num_shots)?;
Ok(samples.into_iter().map(|s| pack_bits(&s)).collect())
}
pub fn sample_batch_measurements_packed(&self, num_shots: usize) -> Result<Vec<Vec<u8>>> {
let samples: Vec<Vec<bool>> = (0..num_shots)
.map(|_| self.sample_measurements())
.collect::<Result<Vec<_>>>()?;
Ok(samples.into_iter().map(|s| pack_bits(&s)).collect())
}
pub fn sample_statistics(&self, num_shots: usize) -> Result<SampleStatistics> {
let samples = self.sample_batch(num_shots)?;
let mut detector_fire_counts = vec![0usize; self.compiled.num_detectors];
let mut measurement_one_counts = vec![0usize; self.compiled.num_measurements];
let mut total_detector_fires = 0;
for result in &samples {
for (i, &val) in result.detector_values.iter().enumerate() {
if val {
detector_fire_counts[i] += 1;
total_detector_fires += 1;
}
}
for (i, &val) in result.measurement_record.iter().enumerate() {
if val {
measurement_one_counts[i] += 1;
}
}
}
Ok(SampleStatistics {
num_shots,
num_detectors: self.compiled.num_detectors,
num_measurements: self.compiled.num_measurements,
detector_fire_counts,
measurement_one_counts,
total_detector_fires,
logical_error_rate: 0.0, })
}
#[must_use]
pub fn num_detectors(&self) -> usize {
self.compiled.num_detectors
}
#[must_use]
pub fn num_measurements(&self) -> usize {
self.compiled.num_measurements
}
#[must_use]
pub fn num_qubits(&self) -> usize {
self.compiled.num_qubits
}
}
#[derive(Debug, Clone)]
pub struct SampleStatistics {
pub num_shots: usize,
pub num_detectors: usize,
pub num_measurements: usize,
pub detector_fire_counts: Vec<usize>,
pub measurement_one_counts: Vec<usize>,
pub total_detector_fires: usize,
pub logical_error_rate: f64,
}
impl SampleStatistics {
#[must_use]
pub fn detector_fire_rate(&self, detector_idx: usize) -> f64 {
if detector_idx < self.detector_fire_counts.len() && self.num_shots > 0 {
self.detector_fire_counts[detector_idx] as f64 / self.num_shots as f64
} else {
0.0
}
}
#[must_use]
pub fn average_detector_fires(&self) -> f64 {
if self.num_shots > 0 {
self.total_detector_fires as f64 / self.num_shots as f64
} else {
0.0
}
}
#[must_use]
pub fn any_detector_fire_rate(&self) -> f64 {
let shots_with_fire = self.detector_fire_counts.iter().filter(|&&c| c > 0).count();
if self.num_shots > 0 {
shots_with_fire as f64 / self.num_shots as f64
} else {
0.0
}
}
}
fn pack_bits(bits: &[bool]) -> Vec<u8> {
bits.chunks(8)
.map(|chunk| {
let mut byte = 0u8;
for (i, &bit) in chunk.iter().enumerate() {
if bit {
byte |= 1 << i;
}
}
byte
})
.collect()
}
fn unpack_bits(bytes: &[u8], num_bits: usize) -> Vec<bool> {
let mut bits = Vec::with_capacity(num_bits);
for (byte_idx, &byte) in bytes.iter().enumerate() {
for bit_idx in 0..8 {
if byte_idx * 8 + bit_idx >= num_bits {
break;
}
bits.push((byte >> bit_idx) & 1 == 1);
}
}
bits
}
pub fn compile_sampler(circuit: &StimCircuit) -> Result<DetectorSampler> {
DetectorSampler::compile(circuit)
}
pub fn compile_sampler_with_dem(circuit: &StimCircuit) -> Result<DetectorSampler> {
DetectorSampler::compile_with_dem(circuit)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compile_sampler() {
let circuit_str = r#"
H 0
CNOT 0 1
M 0 1
DETECTOR rec[-1] rec[-2]
"#;
let circuit = StimCircuit::from_str(circuit_str).unwrap();
let sampler = compile_sampler(&circuit).unwrap();
assert_eq!(sampler.num_qubits(), 2);
assert_eq!(sampler.num_measurements(), 2);
assert_eq!(sampler.num_detectors(), 1);
}
#[test]
fn test_sample_basic() {
let circuit_str = r#"
H 0
CNOT 0 1
M 0 1
"#;
let circuit = StimCircuit::from_str(circuit_str).unwrap();
let sampler = compile_sampler(&circuit).unwrap();
let result = sampler.sample().unwrap();
assert_eq!(result.measurement_record.len(), 2);
assert_eq!(result.measurement_record[0], result.measurement_record[1]);
}
#[test]
fn test_sample_batch() {
let circuit_str = r#"
M 0
"#;
let circuit = StimCircuit::from_str(circuit_str).unwrap();
let sampler = compile_sampler(&circuit).unwrap();
let results = sampler.sample_batch(10).unwrap();
assert_eq!(results.len(), 10);
for result in &results {
assert!(!result.measurement_record[0]);
}
}
#[test]
fn test_sample_detectors() {
let circuit_str = r#"
M 0 1
DETECTOR rec[-1] rec[-2]
"#;
let circuit = StimCircuit::from_str(circuit_str).unwrap();
let sampler = compile_sampler(&circuit).unwrap();
let detectors = sampler.sample_detectors().unwrap();
assert_eq!(detectors.len(), 1);
assert!(!detectors[0]); }
#[test]
fn test_sample_batch_packed() {
let circuit_str = r#"
M 0 1 2 3 4 5 6 7 8
DETECTOR rec[-1] rec[-2]
"#;
let circuit = StimCircuit::from_str(circuit_str).unwrap();
let sampler = compile_sampler(&circuit).unwrap();
let packed = sampler.sample_batch_measurements_packed(5).unwrap();
assert_eq!(packed.len(), 5);
assert_eq!(packed[0].len(), 2);
}
#[test]
fn test_sample_statistics() {
let circuit_str = r#"
M 0
DETECTOR rec[-1]
"#;
let circuit = StimCircuit::from_str(circuit_str).unwrap();
let sampler = compile_sampler(&circuit).unwrap();
let stats = sampler.sample_statistics(100).unwrap();
assert_eq!(stats.num_shots, 100);
assert_eq!(stats.num_detectors, 1);
assert_eq!(stats.num_measurements, 1);
}
#[test]
fn test_pack_unpack_bits() {
let bits = vec![true, false, true, true, false, false, true, false, true];
let packed = pack_bits(&bits);
let unpacked = unpack_bits(&packed, bits.len());
assert_eq!(bits, unpacked);
}
#[test]
fn test_compile_with_dem() {
let circuit_str = r#"
H 0
CNOT 0 1
M 0 1
DETECTOR rec[-1] rec[-2]
"#;
let circuit = StimCircuit::from_str(circuit_str).unwrap();
let sampler = compile_sampler_with_dem(&circuit).unwrap();
assert!(sampler.compiled.has_dem());
}
}