use crate::circuit::Qubit;
use smallvec::SmallVec;
use std::collections::HashMap;
use std::fmt;
use std::fmt::Write;
use std::str::FromStr;
use thiserror::Error;
use time::OffsetDateTime;
const BITS_PER_CHUNK: usize = 64;
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum OutcomeError {
#[error("Invalid character '{1}' in binary string at index {0}")]
InvalidCharacter(usize, char),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct Outcome(pub SmallVec<[u64; 4]>);
impl Outcome {
#[inline(always)]
pub fn new(chunks: SmallVec<[u64; 4]>) -> Self {
Self(chunks)
}
pub fn from_indices(width: usize, indices: impl IntoIterator<Item = usize>) -> Self {
let mut chunks = SmallVec::from_elem(0u64, width.div_ceil(BITS_PER_CHUNK));
for index in indices {
if index < width {
chunks[index / BITS_PER_CHUNK] |= 1u64 << (index % BITS_PER_CHUNK);
}
}
Self(chunks)
}
pub(crate) fn from_lsb_bits(bits: &[bool]) -> Self {
let mut chunks = SmallVec::from_elem(0u64, bits.len().div_ceil(BITS_PER_CHUNK));
for (index, value) in bits.iter().copied().enumerate() {
if value {
chunks[index / BITS_PER_CHUNK] |= 1u64 << (index % BITS_PER_CHUNK);
}
}
Self(chunks)
}
pub fn from_bitstring(s: &str) -> Result<Self, OutcomeError> {
let len = s.len();
if len == 0 {
return Ok(Self(SmallVec::new()));
}
let num_chunks = len.div_ceil(BITS_PER_CHUNK);
let mut chunks = smallvec::smallvec![0u64; num_chunks];
for (qubit_index, c) in s.chars().rev().enumerate() {
match c {
'1' => {
let chunk_idx = qubit_index / BITS_PER_CHUNK;
let bit_idx = qubit_index % BITS_PER_CHUNK;
chunks[chunk_idx] |= 1u64 << bit_idx;
}
'0' => {
}
_ => {
let original_index = len - 1 - qubit_index;
return Err(OutcomeError::InvalidCharacter(original_index, c));
}
}
}
Ok(Self(chunks))
}
#[inline]
pub fn is_one(&self, index: usize) -> bool {
let chunk_idx = index / BITS_PER_CHUNK;
let bit_idx = index % BITS_PER_CHUNK;
if let Some(chunk) = self.0.get(chunk_idx) {
(chunk >> bit_idx) & 1 == 1
} else {
false
}
}
pub fn to_bitstring(&self, num_qubits: usize) -> String {
if num_qubits == 0 {
return String::new();
}
let mut s = String::with_capacity(num_qubits);
let full_chunks = num_qubits / BITS_PER_CHUNK;
let remainder_bits = num_qubits % BITS_PER_CHUNK;
if remainder_bits > 0 {
let chunk = self.0.get(full_chunks).copied().unwrap_or(0);
let mask = if remainder_bits == 64 {
u64::MAX
} else {
(1u64 << remainder_bits) - 1
};
let val = chunk & mask;
write!(&mut s, "{:0width$b}", val, width = remainder_bits).unwrap();
}
for i in (0..full_chunks).rev() {
let chunk = self.0.get(i).copied().unwrap_or(0);
write!(&mut s, "{:064b}", chunk).unwrap();
}
s
}
}
impl FromStr for Outcome {
type Err = OutcomeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_bitstring(s)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Status {
Queued,
Running,
Completed,
Failed { error_msg: String, error_code: i32 },
Cancelled,
}
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Status::Queued => write!(f, "Queued"),
Status::Running => write!(f, "Running"),
Status::Completed => write!(f, "Completed"),
Status::Failed {
error_msg,
error_code,
} => {
write!(f, "Failed (Code {}): {}", error_code, error_msg)
}
Status::Cancelled => write!(f, "Cancelled"),
}
}
}
impl Status {
pub fn is_terminal(&self) -> bool {
matches!(
self,
Status::Completed | Status::Failed { .. } | Status::Cancelled
)
}
pub fn is_success(&self) -> bool {
matches!(self, Status::Completed)
}
}
#[derive(Debug, Clone)]
pub struct ExecutionResult {
task_id: String,
shots: usize,
num_qubits: usize,
qubits: Vec<Qubit>,
status: Status,
created_at: OffsetDateTime,
started_at: Option<OffsetDateTime>,
finished_at: Option<OffsetDateTime>,
backend: Option<String>,
counts: HashMap<Outcome, usize>,
probabilities: Option<HashMap<Outcome, f64>>,
}
impl ExecutionResult {
pub fn new(
task_id: String,
qubits: Vec<Qubit>,
shots: usize,
num_qubits: usize,
backend: Option<String>,
created_at: Option<OffsetDateTime>,
) -> Self {
Self {
task_id,
status: Status::Queued,
shots,
qubits,
num_qubits,
created_at: created_at.unwrap_or(OffsetDateTime::now_utc()),
started_at: None,
finished_at: None,
backend,
counts: HashMap::new(),
probabilities: None,
}
}
pub fn from_counts(
task_id: String,
qubits: Vec<Qubit>,
shots: usize,
num_qubits: usize,
backend: Option<String>,
counts: HashMap<Outcome, usize>,
) -> Self {
let mut result = Self::new(task_id, qubits, shots, num_qubits, backend, None);
result.start(None).finish(counts, None).calc_probabilities();
result
}
pub fn start(&mut self, t: Option<OffsetDateTime>) -> &mut Self {
self.started_at = t.or(Some(OffsetDateTime::now_utc()));
self.status = Status::Running;
self
}
pub fn finish(
&mut self,
counts: HashMap<Outcome, usize>,
t: Option<OffsetDateTime>,
) -> &mut Self {
self.counts = counts;
self.probabilities = None;
self.status = Status::Completed;
self.finished_at = t.or(Some(OffsetDateTime::now_utc()));
self
}
pub fn fail(&mut self, msg: String, code: i32) {
self.status = Status::Failed {
error_msg: msg,
error_code: code,
};
}
pub fn cancel(&mut self) {
self.status = Status::Cancelled;
}
pub fn calc_probabilities(&mut self) -> &mut Self {
let total_observed: usize = self.counts.values().sum();
if total_observed == 0 {
self.probabilities = None;
} else {
let mut probs = HashMap::with_capacity(self.counts.len());
let inv_total = 1.0 / (total_observed as f64);
for (outcome, &count) in &self.counts {
probs.insert(outcome.clone(), count as f64 * inv_total);
}
self.probabilities = Some(probs);
}
self
}
pub fn task_id(&self) -> &str {
self.task_id.as_str()
}
pub fn shots(&self) -> usize {
self.shots
}
pub fn num_qubits(&self) -> usize {
self.num_qubits
}
pub fn qubits(&self) -> &Vec<Qubit> {
&self.qubits
}
pub fn status(&self) -> &Status {
&self.status
}
pub fn created_at(&self) -> &OffsetDateTime {
&self.created_at
}
pub fn started_at(&self) -> &Option<OffsetDateTime> {
&self.started_at
}
pub fn finished_at(&self) -> &Option<OffsetDateTime> {
&self.finished_at
}
pub fn backend(&self) -> Option<&String> {
self.backend.as_ref()
}
pub fn counts(&self) -> &HashMap<Outcome, usize> {
&self.counts
}
pub fn probabilities(&self) -> &Option<HashMap<Outcome, f64>> {
&self.probabilities
}
}
#[cfg(test)]
mod api_tests {
use super::{ExecutionResult, Outcome, Status};
use crate::circuit::Qubit;
use std::collections::HashMap;
#[test]
fn outcome_from_indices_uses_little_endian_bit_indices() {
let outcome = Outcome::from_indices(66, [0, 2, 65, 99]);
assert!(outcome.is_one(0));
assert!(!outcome.is_one(1));
assert!(outcome.is_one(2));
assert!(outcome.is_one(65));
assert!(!outcome.is_one(99));
}
#[test]
fn execution_result_from_counts_returns_completed_result_with_probabilities() {
let zero = Outcome::from_indices(1, []);
let one = Outcome::from_indices(1, [0]);
let mut counts = HashMap::new();
counts.insert(zero.clone(), 3);
counts.insert(one.clone(), 1);
let result = ExecutionResult::from_counts(
"task".to_string(),
vec![Qubit::new(0)],
4,
1,
Some("backend".to_string()),
counts,
);
assert_eq!(result.status(), &Status::Completed);
assert_eq!(result.counts().get(&zero), Some(&3));
assert_eq!(result.counts().get(&one), Some(&1));
let probabilities = result.probabilities().as_ref().unwrap();
assert_eq!(probabilities.get(&zero), Some(&0.75));
assert_eq!(probabilities.get(&one), Some(&0.25));
}
}
#[cfg(test)]
#[path = "./result_test.rs"]
mod result_test;