use std::fmt;
use std::num::{NonZeroU32, NonZeroU64};
use std::sync::atomic::{AtomicU64, Ordering};
use crate::circuit::bit::Qubit;
use crate::circuit::classical_expr::ClassicalExpr;
use crate::device::Outcome;
use crate::qis::error::QisError;
use smallvec::SmallVec;
static NEXT_CIRCUIT_ID: AtomicU64 = AtomicU64::new(1);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct CircuitId(NonZeroU64);
impl fmt::Display for CircuitId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "CircuitId({})", self.0)
}
}
impl CircuitId {
pub fn new() -> Self {
let id = NEXT_CIRCUIT_ID
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |current| {
current.checked_add(1).filter(|next| *next != u64::MAX)
})
.unwrap_or_else(|_| panic!("circuit identity space exhausted"));
Self(NonZeroU64::new(id).expect("circuit identity counter must remain non-zero"))
}
}
impl Default for CircuitId {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ClassicalType {
Bit,
Bool,
UInt(NonZeroU32),
BitVec(NonZeroU32),
}
impl ClassicalType {
pub fn uint(width: u32) -> Option<Self> {
NonZeroU32::new(width).map(Self::UInt)
}
pub fn bit_vec(width: u32) -> Option<Self> {
NonZeroU32::new(width).map(Self::BitVec)
}
pub fn width(self) -> u32 {
match self {
Self::Bit | Self::Bool => 1,
Self::UInt(width) | Self::BitVec(width) => width.get(),
}
}
pub fn zero_literal(self) -> Result<ClassicalExpr, crate::circuit::CircuitError> {
match self {
Self::Bool => Ok(ClassicalExpr::bool_literal(false)),
Self::Bit => Ok(ClassicalExpr::bit_literal(false)),
Self::UInt(width) => ClassicalExpr::uint_literal(width.get(), 0),
Self::BitVec(width) => ClassicalExpr::bit_vec_literal(width.get(), 0),
}
}
pub fn one_literal(self) -> Result<ClassicalExpr, crate::circuit::CircuitError> {
match self {
Self::Bool => Ok(ClassicalExpr::bool_literal(true)),
Self::Bit => Ok(ClassicalExpr::bit_literal(true)),
Self::UInt(width) => ClassicalExpr::uint_literal(width.get(), 1),
Self::BitVec(width) => ClassicalExpr::bit_vec_literal(width.get(), 1),
}
}
pub fn measurement_width(self) -> Option<u32> {
match self {
Self::Bit => Some(1),
Self::BitVec(width) => Some(width.get()),
Self::Bool | Self::UInt(_) => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ClassicalVar {
circuit_id: CircuitId,
index: u32,
ty: ClassicalType,
}
impl ClassicalVar {
pub fn new(circuit_id: CircuitId, index: u32, ty: ClassicalType) -> Self {
Self {
circuit_id,
index,
ty,
}
}
pub fn id(self) -> u32 {
self.index
}
pub fn index(self) -> u32 {
self.index
}
pub fn circuit_id(self) -> CircuitId {
self.circuit_id
}
pub fn ty(self) -> ClassicalType {
self.ty
}
pub fn expr(self) -> ClassicalExpr {
ClassicalExpr::var(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ClassicalValue {
circuit_id: CircuitId,
index: u32,
ty: ClassicalType,
}
impl ClassicalValue {
pub fn new(circuit_id: CircuitId, index: u32, ty: ClassicalType) -> Self {
Self {
circuit_id,
index,
ty,
}
}
pub fn index(self) -> u32 {
self.index
}
pub fn circuit_id(self) -> CircuitId {
self.circuit_id
}
pub fn ty(self) -> ClassicalType {
self.ty
}
pub fn expr(self) -> ClassicalExpr {
ClassicalExpr::value(self)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Measurement {
value: ClassicalValue,
qubits: SmallVec<[Qubit; 3]>,
}
impl Measurement {
pub fn new(value: ClassicalValue, qubits: SmallVec<[Qubit; 3]>) -> Self {
Self { value, qubits }
}
pub fn value(&self) -> ClassicalValue {
self.value
}
pub fn expr(&self) -> ClassicalExpr {
self.value.expr()
}
pub fn qubits(&self) -> &[Qubit] {
&self.qubits
}
pub fn width(&self) -> usize {
self.qubits.len()
}
pub fn ty(&self) -> ClassicalType {
self.value.ty()
}
pub fn check_qubits(&self, num_qubits: usize) -> Result<(), QisError> {
for qubit in self.qubits() {
let index = qubit.index();
if index >= num_qubits {
return Err(QisError::IndexOutOfBounds {
index,
max: num_qubits.saturating_sub(1),
});
}
}
Ok(())
}
pub fn project(&self, full: &Outcome) -> Outcome {
Outcome::from_indices(
self.width(),
self.qubits()
.iter()
.enumerate()
.filter_map(|(bit, qubit)| full.is_one(qubit.index()).then_some(bit)),
)
}
pub fn project_basis(&self, basis: usize) -> Outcome {
Outcome::from_indices(
self.width(),
self.qubits()
.iter()
.enumerate()
.filter_map(|(bit, qubit)| (((basis >> qubit.index()) & 1) == 1).then_some(bit)),
)
}
}
#[cfg(test)]
mod tests {
use super::{CircuitId, ClassicalType, ClassicalValue, ClassicalVar, Measurement};
use crate::circuit::Qubit;
use crate::device::Outcome;
use crate::qis::QisError;
use smallvec::smallvec;
#[test]
fn type_widths_are_reported() {
assert_eq!(ClassicalType::Bit.width(), 1);
assert_eq!(ClassicalType::Bool.width(), 1);
assert_eq!(ClassicalType::uint(7).unwrap().width(), 7);
assert_eq!(ClassicalType::bit_vec(3).unwrap().width(), 3);
}
#[test]
fn zero_width_integer_and_bit_vector_are_rejected() {
assert_eq!(ClassicalType::uint(0), None);
assert_eq!(ClassicalType::bit_vec(0), None);
}
#[test]
fn measurement_width_accepts_only_bits_and_bit_vectors() {
assert_eq!(ClassicalType::Bit.measurement_width(), Some(1));
assert_eq!(
ClassicalType::bit_vec(5).unwrap().measurement_width(),
Some(5)
);
assert_eq!(ClassicalType::Bool.measurement_width(), None);
assert_eq!(ClassicalType::uint(5).unwrap().measurement_width(), None);
}
#[test]
fn variables_expose_id_and_type() {
let var = ClassicalVar::new(CircuitId::new(), 12, ClassicalType::bit_vec(4).unwrap());
assert_eq!(var.id(), 12);
assert_eq!(var.ty(), ClassicalType::bit_vec(4).unwrap());
}
#[test]
fn variable_identity_includes_id_and_type() {
let circuit_id = CircuitId::new();
let bit = ClassicalVar::new(circuit_id, 1, ClassicalType::Bit);
let same_bit = ClassicalVar::new(circuit_id, 1, ClassicalType::Bit);
let bool_var = ClassicalVar::new(circuit_id, 1, ClassicalType::Bool);
let other_bit = ClassicalVar::new(circuit_id, 2, ClassicalType::Bit);
assert_eq!(bit, same_bit);
assert_ne!(bit, bool_var);
assert_ne!(bit, other_bit);
}
#[test]
fn measurement_exposes_value_and_qubit_order() {
let value = ClassicalValue::new(CircuitId::new(), 3, ClassicalType::bit_vec(2).unwrap());
let measurement = Measurement::new(value, smallvec![Qubit::new(1), Qubit::new(0)]);
assert_eq!(measurement.value(), value);
assert_eq!(measurement.ty(), ClassicalType::bit_vec(2).unwrap());
assert_eq!(measurement.width(), 2);
assert_eq!(measurement.qubits(), &[Qubit::new(1), Qubit::new(0)]);
assert_eq!(measurement.expr().ty(), ClassicalType::bit_vec(2).unwrap());
}
#[test]
fn measurement_projects_outcomes_in_measurement_order() {
let value = ClassicalValue::new(CircuitId::new(), 3, ClassicalType::bit_vec(3).unwrap());
let measurement = Measurement::new(
value,
smallvec![Qubit::new(2), Qubit::new(0), Qubit::new(1)],
);
let full = Outcome::from_indices(3, [0, 2]);
let projected = measurement.project(&full);
assert_eq!(projected.to_bitstring(3), "011");
assert_eq!(measurement.project_basis(0b101).to_bitstring(3), "011");
}
#[test]
fn measurement_checks_qubit_bounds() {
let value = ClassicalValue::new(CircuitId::new(), 3, ClassicalType::bit_vec(2).unwrap());
let measurement = Measurement::new(value, smallvec![Qubit::new(0), Qubit::new(2)]);
assert!(measurement.check_qubits(3).is_ok());
assert!(matches!(
measurement.check_qubits(2),
Err(QisError::IndexOutOfBounds { index: 2, max: 1 })
));
}
}