use crate::misc::bit_vector;
use crate::misc::python::{get_or_load_module, get_or_load_module_from_source, json_value_to_py};
use crate::simulator::DeterministicRng;
use crate::simulator::common::{ErrorSet, Sampler};
use crate::util::BitVector;
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
#[cfg(feature = "cli")]
use structdoc::StructDoc;
mod builtin_samplers {
pub fn lookup(name: &str) -> Option<(&'static str, &'static str)> {
match name {
"qdk_sampler" => Some(("@qdk_sampler", include_str!("qdk_sampler.py"))),
_ => None,
}
}
pub fn names() -> &'static [&'static str] {
&["qdk_sampler"]
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "cli", derive(StructDoc))]
pub struct PythonSamplerConfig {
pub sampler: String,
#[serde(default = "default_class_name")]
pub name: String,
#[cfg_attr(feature = "cli", structdoc(skip))]
#[serde(default)]
pub py_config: Option<serde_json::Value>,
}
fn default_class_name() -> String {
"Sampler".to_string()
}
pub struct PythonSampler {
instance: Mutex<Py<PyAny>>,
num_measurements: usize,
}
impl PythonSampler {
pub fn new(
circuit_text: &str,
config: &PythonSamplerConfig,
seed: u64,
skip_shots: usize,
num_measurements: usize,
) -> Self {
let mut py_cfg_json = config.py_config.clone().unwrap_or_else(|| serde_json::json!({}));
if let serde_json::Value::Object(ref mut map) = py_cfg_json {
map.entry("seed".to_string()).or_insert(serde_json::json!(seed));
map.entry("skip_shots".to_string()).or_insert(serde_json::json!(skip_shots));
map.entry("num_measurements".to_string())
.or_insert(serde_json::json!(num_measurements));
} else {
panic!("py_config must be a JSON object, got: {py_cfg_json}");
}
let instance = Python::attach(|py| -> PyResult<Py<PyAny>> {
let module = if let Some(builtin_name) = config.sampler.strip_prefix('@') {
let (fname, source) = builtin_samplers::lookup(builtin_name).ok_or_else(|| {
let known = builtin_samplers::names()
.iter()
.map(|n| format!("@{n}"))
.collect::<Vec<_>>()
.join(", ");
PyValueError::new_err(format!("unknown builtin sampler '@{builtin_name}'. Known builtins: {known}"))
})?;
get_or_load_module_from_source(py, fname, source)?
} else {
get_or_load_module(py, &config.sampler)?
};
let sampler_class = module.getattr(config.name.as_str())?;
let py_cfg = json_value_to_py(py, &py_cfg_json)?;
let inst = sampler_class.call1((circuit_text, py_cfg))?;
Ok(inst.unbind())
})
.expect("failed to construct Python sampler");
Self {
instance: Mutex::new(instance),
num_measurements,
}
}
fn next_shot_string(&self) -> String {
let guard = self.instance.lock().expect("PythonSampler mutex poisoned");
Python::attach(|py| -> PyResult<String> {
let inst = guard.bind(py);
let py_result = inst.call_method0("sample")?;
py_result.extract::<String>()
})
.expect("Python sampler.sample() raised an exception")
}
}
impl Sampler for PythonSampler {
fn sample(&self, _rng: &mut DeterministicRng) -> ErrorSet {
let shot = self.next_shot_string();
assert_eq!(
shot.chars().count(),
self.num_measurements,
"Python sampler returned {} chars, expected {} measurement chars",
shot.chars().count(),
self.num_measurements
);
let mut bits: Vec<bool> = Vec::with_capacity(self.num_measurements);
let mut loss_flags: Vec<bool> = Vec::with_capacity(self.num_measurements);
for c in shot.chars() {
match c {
'0' => {
bits.push(false);
loss_flags.push(false);
}
'1' => {
bits.push(true);
loss_flags.push(false);
}
'-' => {
bits.push(false);
loss_flags.push(true);
}
other => panic!("Python sampler returned invalid char {other:?} (expected '0', '1', or '-')"),
}
}
ErrorSet {
errors: vec![],
measurements: BitVector {
size: bits.len() as u64,
data: bit_vector::pack_bits(&bits),
},
loss_mask: Some(BitVector {
size: loss_flags.len() as u64,
data: bit_vector::pack_bits(&loss_flags),
}),
}
}
fn sample_single_error(&self, _index: usize) -> ErrorSet {
panic!("sample_single_error is not supported for PythonSampler")
}
fn count_single_error(&self) -> usize {
panic!("count_single_error is not supported for PythonSampler")
}
fn readouts_match(&self, _actual: &BitVector, _expected: &BitVector) -> bool {
true
}
fn error_tag(&self, _marginal_index: usize, _error_index: usize) -> &str {
""
}
}