use crate::bin;
use crate::controller::JitController;
use crate::coordinator;
use crate::jit;
use crate::server::{LocalServer, ServerConfigs};
use clap::Parser;
use prost::Message;
use pyo3::exceptions::{PyAttributeError, PyRuntimeError, PyValueError};
use pyo3::prelude::*;
use std::sync::Arc;
use tonic::Status;
#[pyclass(name = "Runtime")]
pub struct PyRuntime {
inner: Arc<LocalServer>,
}
#[pymethods]
impl PyRuntime {
#[new]
#[pyo3(signature = (
*,
decoder = None,
decoder_config = None,
coordinator = None,
coordinator_config = None,
controller = None,
controller_config = None,
))]
fn new(
py: Python<'_>,
decoder: Option<&str>,
decoder_config: Option<&str>,
coordinator: Option<&str>,
coordinator_config: Option<&str>,
controller: Option<&str>,
controller_config: Option<&str>,
) -> PyResult<Self> {
let configs = parse_configs(
decoder,
decoder_config,
coordinator,
coordinator_config,
controller,
controller_config,
)?;
let inner = py.detach(|| {
let rt = pyo3_async_runtimes::tokio::get_runtime();
rt.block_on(async move { configs.build_local().await })
});
Ok(Self { inner })
}
#[pyo3(signature = (addr = "[::]:0".to_string()))]
fn bind<'py>(&self, py: Python<'py>, addr: String) -> PyResult<Bound<'py, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let parsed: core::net::SocketAddr = addr
.parse()
.map_err(|e: core::net::AddrParseError| PyValueError::new_err(format!("invalid addr {addr:?}: {e}")))?;
inner
.bind_grpc(parsed)
.await
.map_err(|e| PyRuntimeError::new_err(e.to_string()))
})
}
fn bound_port(&self) -> Option<u16> {
self.inner.bound_port()
}
fn shutdown<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let inner = self.inner.clone();
pyo3_async_runtimes::tokio::future_into_py(
py,
async move { inner.shutdown().await.map_err(PyRuntimeError::new_err) },
)
}
#[getter]
fn coordinator(&self) -> PyCoordinator {
PyCoordinator {
inner: self.inner.clone(),
}
}
#[getter]
fn jit_controller(&self) -> PyResult<PyJitController> {
match self.inner.jit_controller() {
Some(controller) => Ok(PyJitController { controller }),
None => Err(PyAttributeError::new_err(
"jit_controller is not configured; \
pass controller=\"jit\" to Runtime(...) to enable it",
)),
}
}
fn has_jit_controller(&self) -> bool {
self.inner.jit_controller().is_some()
}
fn __repr__(&self) -> String {
let bound = self.inner.bound_port();
let jit = if self.inner.jit_controller().is_some() {
", controller=jit"
} else {
""
};
match bound {
Some(port) => format!("Runtime(bound=:{port}{jit})"),
None => format!("Runtime(unbound{jit})"),
}
}
}
#[pyclass(name = "Coordinator")]
pub struct PyCoordinator {
inner: Arc<LocalServer>,
}
#[pymethods]
impl PyCoordinator {
fn load_library<'py>(&self, py: Python<'py>, library: Vec<u8>) -> PyResult<Bound<'py, PyAny>> {
let client = self.inner.coordinator_client();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let library = bin::Library::decode(library.as_slice())
.map_err(|e| PyValueError::new_err(format!("failed to decode Library: {e}")))?;
client.load_library(library).await.map_err(status_to_pyerr)
})
}
fn execute<'py>(&self, py: Python<'py>, instruction: Vec<u8>) -> PyResult<Bound<'py, PyAny>> {
let client = self.inner.coordinator_client();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let instruction = bin::Instruction::decode(instruction.as_slice())
.map_err(|e| PyValueError::new_err(format!("failed to decode Instruction: {e}")))?;
client.execute(instruction).await.map(|resp| resp.id).map_err(status_to_pyerr)
})
}
fn decode<'py>(&self, py: Python<'py>, outcomes: Vec<u8>) -> PyResult<Bound<'py, PyAny>> {
let client = self.inner.coordinator_client();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let outcomes = coordinator::Outcomes::decode(outcomes.as_slice())
.map_err(|e| PyValueError::new_err(format!("failed to decode Outcomes: {e}")))?;
let readouts = client.decode(outcomes).await.map_err(status_to_pyerr)?;
Ok(encode_message(&readouts))
})
}
#[pyo3(signature = (reset_library = false, reset_decoder_service = false, custom = String::new()))]
fn reset<'py>(
&self,
py: Python<'py>,
reset_library: bool,
reset_decoder_service: bool,
custom: String,
) -> PyResult<Bound<'py, PyAny>> {
let client = self.inner.coordinator_client();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let req = coordinator::ResetRequest {
reset_library,
reset_decoder_service,
custom,
};
client.reset(req).await.map_err(status_to_pyerr)
})
}
fn __repr__(&self) -> String {
"Coordinator()".to_string()
}
}
#[pyclass(name = "JitController")]
pub struct PyJitController {
controller: Arc<JitController>,
}
#[pymethods]
impl PyJitController {
fn load_library<'py>(&self, py: Python<'py>, library: Vec<u8>) -> PyResult<Bound<'py, PyAny>> {
let controller = self.controller.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let library = jit::JitLibrary::decode(library.as_slice())
.map_err(|e| PyValueError::new_err(format!("failed to decode JitLibrary: {e}")))?;
controller.load_library(library).await.map_err(status_to_pyerr)
})
}
fn execute<'py>(&self, py: Python<'py>, instruction: Vec<u8>) -> PyResult<Bound<'py, PyAny>> {
let controller = self.controller.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let instruction = jit::JitInstruction::decode(instruction.as_slice())
.map_err(|e| PyValueError::new_err(format!("failed to decode JitInstruction: {e}")))?;
Ok(controller.execute(instruction).await)
})
}
fn batch_execute<'py>(&self, py: Python<'py>, instructions: Vec<Vec<u8>>) -> PyResult<Bound<'py, PyAny>> {
let controller = self.controller.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let decoded: Vec<jit::JitInstruction> = instructions
.into_iter()
.enumerate()
.map(|(idx, bytes)| {
jit::JitInstruction::decode(bytes.as_slice())
.map_err(|e| PyValueError::new_err(format!("failed to decode JitInstruction[{idx}]: {e}")))
})
.collect::<PyResult<_>>()?;
controller
.batch_execute(decoded)
.await
.map_err(|e| PyValueError::new_err(e.to_string()))
})
}
fn decode<'py>(&self, py: Python<'py>, outcomes: Vec<u8>) -> PyResult<Bound<'py, PyAny>> {
let controller = self.controller.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let outcomes = coordinator::Outcomes::decode(outcomes.as_slice())
.map_err(|e| PyValueError::new_err(format!("failed to decode Outcomes: {e}")))?;
let readouts = controller.decode_single(outcomes).await.map_err(status_to_pyerr)?;
Ok(encode_message(&readouts))
})
}
fn batch_decode<'py>(&self, py: Python<'py>, outcomes_list: Vec<Vec<u8>>) -> PyResult<Bound<'py, PyAny>> {
let controller = self.controller.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let decoded: Vec<coordinator::Outcomes> = outcomes_list
.into_iter()
.enumerate()
.map(|(idx, bytes)| {
coordinator::Outcomes::decode(bytes.as_slice())
.map_err(|e| PyValueError::new_err(format!("failed to decode Outcomes[{idx}]: {e}")))
})
.collect::<PyResult<_>>()?;
let readouts = controller.batch_decode(decoded).await.map_err(status_to_pyerr)?;
Ok(readouts.iter().map(encode_message).collect::<Vec<_>>())
})
}
#[pyo3(signature = (reset_library = false, reset_decoder_service = false, custom = String::new()))]
fn reset<'py>(
&self,
py: Python<'py>,
reset_library: bool,
reset_decoder_service: bool,
custom: String,
) -> PyResult<Bound<'py, PyAny>> {
let controller = self.controller.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move {
let req = coordinator::ResetRequest {
reset_library,
reset_decoder_service,
custom,
};
controller.reset(req).await.map_err(status_to_pyerr)
})
}
fn __repr__(&self) -> String {
"JitController()".to_string()
}
}
fn encode_message<M: Message>(msg: &M) -> Vec<u8> {
let mut buf = Vec::with_capacity(msg.encoded_len());
msg.encode(&mut buf).expect("encode protobuf message");
buf
}
fn status_to_pyerr(status: Status) -> PyErr {
PyRuntimeError::new_err(format!("{}: {}", status.code(), status.message()))
}
fn parse_configs(
decoder: Option<&str>,
decoder_config: Option<&str>,
coordinator: Option<&str>,
coordinator_config: Option<&str>,
controller: Option<&str>,
controller_config: Option<&str>,
) -> PyResult<ServerConfigs> {
let mut argv: Vec<String> = vec!["deq-runtime-python".to_string()];
if let Some(value) = decoder {
argv.push("--decoder".into());
argv.push(value.into());
}
if let Some(value) = decoder_config {
argv.push("--decoder-config".into());
argv.push(value.into());
}
if let Some(value) = coordinator {
argv.push("--coordinator".into());
argv.push(value.into());
}
if let Some(value) = coordinator_config {
argv.push("--coordinator-config".into());
argv.push(value.into());
}
if let Some(value) = controller {
argv.push("--controller".into());
argv.push(value.into());
}
if let Some(value) = controller_config {
argv.push("--controller-config".into());
argv.push(value.into());
}
ServerConfigs::try_parse_from(argv).map_err(|e| PyValueError::new_err(e.to_string()))
}
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<PyRuntime>()?;
m.add_class::<PyCoordinator>()?;
m.add_class::<PyJitController>()?;
#[cfg(feature = "simulator")]
m.add_class::<PySampler>()?;
Ok(())
}
#[cfg(feature = "simulator")]
#[pyclass(name = "Sampler")]
pub struct PySampler {
inner: Arc<dyn crate::simulator::common::Sampler>,
}
#[cfg(feature = "simulator")]
#[pymethods]
impl PySampler {
#[new]
#[pyo3(signature = (circuit, *, simulator = None, simulator_config = None, seed = None, skip_shots = 0))]
fn new(
circuit: &str,
simulator: Option<&str>,
simulator_config: Option<&str>,
seed: Option<u64>,
skip_shots: usize,
) -> PyResult<Self> {
use crate::simulator::common::SamplerType;
use rand::Rng;
let name = simulator.unwrap_or("stim");
let sampler_type = SamplerType::from_name(name).ok_or_else(|| {
PyValueError::new_err(format!(
"unknown simulator {name:?}; valid: {:?}",
SamplerType::variant_names()
))
})?;
let config: serde_json::Value = match simulator_config {
Some(s) if !s.is_empty() => {
serde_json::from_str(s).map_err(|e| PyValueError::new_err(format!("invalid simulator_config JSON: {e}")))?
}
_ => serde_json::json!({}),
};
let seed = seed.unwrap_or_else(|| rand::rng().next_u64());
let inner = sampler_type
.create(circuit, seed, skip_shots, config)
.map_err(PyValueError::new_err)?;
Ok(Self { inner })
}
fn sample<'py>(&self, py: Python<'py>, num_shots: usize) -> PyResult<Vec<Bound<'py, PyAny>>> {
use crate::simulator::DeterministicRng;
use pyo3::types::PyBytes;
use rand::SeedableRng;
let inner = self.inner.clone();
let shots: Vec<Vec<u8>> = py.detach(|| {
let mut rng = DeterministicRng::seed_from_u64(0);
(0..num_shots)
.map(|_| {
let error_set = inner.sample(&mut rng);
let shot = crate::simulator::common::error_set_to_shot_sample(&error_set);
let mut buf = Vec::with_capacity(shot.encoded_len());
shot.encode(&mut buf).expect("encoding ShotSample cannot fail");
buf
})
.collect()
});
Ok(shots.into_iter().map(|bytes| PyBytes::new(py, &bytes).into_any()).collect())
}
fn __repr__(&self) -> String {
"Sampler()".to_string()
}
}