From aae4c5ef5ed4908eb9d3b1c440e0d95b0ef6b645 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez Date: Thu, 16 Jan 2025 13:35:07 -0500 Subject: [PATCH] `BitData` refactor: Store Registers --- crates/circuit/src/bit.rs | 161 +---------- crates/circuit/src/bit_data.rs | 399 +++++++++++++++++++++++++- crates/circuit/src/imports.rs | 2 + crates/circuit/src/lib.rs | 28 +- crates/circuit/src/register.rs | 505 ++++++--------------------------- 5 files changed, 514 insertions(+), 581 deletions(-) diff --git a/crates/circuit/src/bit.rs b/crates/circuit/src/bit.rs index 469bf22a340f..8580c7651934 100644 --- a/crates/circuit/src/bit.rs +++ b/crates/circuit/src/bit.rs @@ -1,168 +1,21 @@ -use std::hash::{DefaultHasher, Hash, Hasher}; - -use pyo3::{exceptions::PyTypeError, prelude::*, types::PyDict}; - -use crate::{ - circuit_data::CircuitError, - interner::Interned, - register::{Register, RegisterAsKey}, -}; - -/// Object representing a Python bit, that allows us to keep backwards compatibility -/// with the previous structure. -#[pyclass(name = "Bit", module = "qiskit._accelerate.bit", subclass)] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct PyBit { - register: Option, // Register identifier - index: Option, // Index within Register -} - -#[pymethods] -impl PyBit { - #[new] - #[pyo3(signature=(register=None, index=None))] - pub fn new(register: Option, index: Option) -> PyResult { - match (®ister, index) { - (None, None) => Ok(Self { register, index }), - (Some(_), Some(_)) => Ok(Self { register, index }), - _ => Err(CircuitError::new_err( - "You should provide both an index and a register, not just one of them.", - )), - } - } - - fn __eq__<'py>(slf: Bound<'py, Self>, other: Bound<'py, Self>) -> bool { - let borrowed = slf.borrow(); - let other_borrowed = other.borrow(); - if borrowed.register.is_some() && borrowed.index.is_some() { - return borrowed.register == other_borrowed.register - && borrowed.index == other_borrowed.index; - } - - slf.is(&other) - } - - fn __hash__(&self, py: Python<'_>) -> PyResult { - if let (Some(reg), Some(idx)) = (self.register.as_ref(), self.index) { - return (reg.reduce(), idx).to_object(py).bind(py).hash(); - } - - // If registers are unavailable, hash by pointer value. - let mut hasher = DefaultHasher::new(); - let pointer_val = self as *const Self; - pointer_val.hash(&mut hasher); - Ok(hasher.finish() as isize) - } - - fn __copy__(slf: Bound) -> Bound { - slf - } - - #[pyo3(signature = (_memo=None))] - fn __deepcopy__<'py>( - slf: Bound<'py, Self>, - _memo: Option>, - ) -> PyResult> { - let borrowed: PyRef = slf.borrow(); - if borrowed.index.is_none() && borrowed.register.is_none() { - return Ok(slf); - } - let copy = slf - .get_type() - .call_method1("__new__", (slf.get_type(),))? - .downcast_into::()?; - let mut copy_mut = copy.borrow_mut(); - copy_mut.register = borrowed.register.clone(); - copy_mut.index = borrowed.index; - Ok(copy) - } - - fn __getstate__(slf: PyRef<'_, Self>) -> (Option<(String, u32)>, Option) { - ( - slf.register.as_ref().map(|reg| { - let (name, num_qubits) = reg.reduce(); - (name.to_string(), num_qubits) - }), - slf.index.as_ref().copied(), - ) - } - - fn __setstate__(mut slf: PyRefMut<'_, Self>, state: (Option<(String, u32)>, Option)) { - slf.register = state - .0 - .map(|(name, num_qubits)| RegisterAsKey::Register((name, num_qubits))); - slf.index = state.1; - } - - fn __repr__(slf: Bound) -> PyResult { - let borrowed = slf.borrow(); - if borrowed.register.is_none() && borrowed.index.is_none() { - return Ok(slf.py_super()?.repr()?.to_string()); - } - let reg = borrowed.register.as_ref().unwrap(); - Ok(format!( - "{}({}({}, '{}'), {})", - slf.get_type().name()?, - reg.type_identifier(), - reg.index(), - reg.name(), - borrowed.index.unwrap() - )) - } - - pub fn is_new(&self) -> bool { - self.index.is_none() && self.register.is_none() - } -} - -macro_rules! create_py_bit { - ($name:ident, $pyname:literal, $reg_type:pat, $module:literal) => { - #[pyclass(name=$pyname, extends=PyBit, subclass, module=$module)] - #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] - pub struct $name(); - - #[pymethods] - impl $name { - #[new] - #[pyo3(signature = (register=None, index=None))] - pub fn py_new( - register: Option, - index: Option, - ) -> PyResult<(Self, PyBit)> { - if register.is_none() || matches!(register, Some($reg_type)) { - Ok((Self(), PyBit::new(register, index)?)) - } else { - Err(PyTypeError::new_err(format!( - "The incorrect register was assigned. Bit type {}, Register type {}", - $pyname, - register.unwrap().type_identifier() - ))) - } - } - } - }; -} - -// Create python instances -create_py_bit! {PyQubit, "Qubit", RegisterAsKey::Quantum(_), "qiskit._accelerate.bit"} -create_py_bit! {PyClbit, "Clbit", RegisterAsKey::Classical(_), "qiskit._accelerate.bit"} +use std::fmt::Debug; /// Keeps information about where a qubit is located within the circuit. -#[derive(Debug, Clone)] -pub struct BitInfo { - register_idx: Interned, +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub struct BitInfo { + register_idx: u32, index: u32, } -impl BitInfo { - pub fn new(register_idx: Interned, index: u32) -> Self { +impl BitInfo { + pub fn new(register_idx: u32, index: u32) -> Self { Self { register_idx, index, } } - pub fn register_index(&self) -> Interned { + pub fn register_index(&self) -> u32 { self.register_idx } diff --git a/crates/circuit/src/bit_data.rs b/crates/circuit/src/bit_data.rs index 56a2560385f2..c56b1003e5ee 100644 --- a/crates/circuit/src/bit_data.rs +++ b/crates/circuit/src/bit_data.rs @@ -10,13 +10,19 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use crate::BitType; +use crate::bit::BitInfo; +use crate::imports::{CLASSICAL_REGISTER, QUANTUM_REGISTER, REGISTER}; +use crate::register::{Register, RegisterAsKey}; +use crate::{BitType, ToPyBit}; use hashbrown::HashMap; +use indexmap::{Equivalent, IndexSet}; use pyo3::exceptions::{PyKeyError, PyRuntimeError, PyValueError}; use pyo3::prelude::*; -use pyo3::types::PyList; +use pyo3::types::{PyDict, PyList}; +use std::borrow::Borrow; use std::fmt::Debug; use std::hash::{Hash, Hasher}; +use std::sync::OnceLock; /// Private wrapper for Python-side Bit instances that implements /// [Hash] and [Eq], allowing them to be used in Rust hash-based @@ -29,7 +35,7 @@ use std::hash::{Hash, Hasher}; /// it call `repr()` on both sides, which has a significant /// performance advantage. #[derive(Clone, Debug)] -struct BitAsKey { +pub(crate) struct BitAsKey { /// Python's `hash()` of the wrapped instance. hash: isize, /// The wrapped instance. @@ -230,3 +236,390 @@ where self.bits.clear(); } } + +#[derive(Clone, Debug)] +pub struct NewBitData, R: Register + Hash + Eq> { + /// The public field name (i.e. `qubits` or `clbits`). + description: String, + /// Registered Python bits. + bits: Vec>, + /// Maps Python bits to native type. + indices: HashMap, + /// Mapping between bit index and its register info + bit_info: Vec>, + /// Registers in the circuit + registry: IndexSet, + /// Registers in Python + registers: Vec>, +} + +impl NewBitData +where + T: From + Copy + Debug + ToPyBit, + R: Register + + Equivalent + + for<'a> Borrow<&'a RegisterAsKey> + + Hash + + Eq + + From<(usize, Option)> + + for<'a> From<&'a [T]> + + for<'a> From<(&'a [T], Option)>, + BitType: From, +{ + pub fn new(description: String) -> Self { + NewBitData { + description, + bits: Vec::new(), + indices: HashMap::new(), + bit_info: Vec::new(), + registry: IndexSet::new(), + registers: Vec::new(), + } + } + + pub fn with_capacity(description: String, capacity: usize) -> Self { + NewBitData { + description, + bits: Vec::with_capacity(capacity), + indices: HashMap::with_capacity(capacity), + bit_info: Vec::with_capacity(capacity), + registry: IndexSet::with_capacity(capacity), + registers: Vec::with_capacity(capacity), + } + } + + /// Gets the number of bits. + pub fn len(&self) -> usize { + self.bits.len() + } + + /// Gets the number of registers. + pub fn len_regs(&self) -> usize { + self.registry.len() + } + + pub fn is_empty(&self) -> bool { + self.bits.is_empty() + } + + /// Gets a reference to the underlying vector of Python bits. + #[inline] + pub fn bits(&self) -> &Vec> { + &self.bits + } + + /// Adds a register onto the [BitData] of the circuit. + pub fn add_register( + &mut self, + name: Option, + size: Option, + bits: Option<&[T]>, + ) -> u32 { + match (size, bits) { + (None, None) => panic!("You should at least provide either a size or the bit indices."), + (None, Some(bits)) => { + let reg: R = (bits, name).into(); + let idx = self.registry.len().try_into().unwrap_or_else(|_| { + panic!( + "The {} registry in this circuit has reached its maximum capacity.", + self.description + ) + }); + // Add register info cancel if any qubit is duplicated + for (bit_idx, bit) in bits.iter().enumerate() { + let bit_info = &mut self.bit_info[BitType::from(*bit) as usize]; + if bit_info.is_some() { + panic!( + "The bit {:?} is currently assigned to another register.", + bit + ) + } else { + *bit_info = Some(BitInfo::new( + idx, + bit_idx.try_into().unwrap_or_else(|_| { + panic!( + "The current register exceeds its capacity limit. Bits {}", + reg.len() + ) + }), + )) + } + } + self.registry.insert(reg); + self.registers.push(OnceLock::new()); + idx + } + (Some(size), None) => { + let bits: Vec = (0..size).map(|_| self.add_bit()).collect(); + let reg = (bits.as_slice(), name).into(); + let idx = self.registry.len().try_into().unwrap_or_else(|_| { + panic!( + "The {} registry in this circuit has reached its maximum capacity.", + self.description + ) + }); + self.registry.insert(reg); + self.registers.push(OnceLock::new()); + idx + } + (Some(_), Some(_)) => { + panic!("You should only provide either a size or the bit indices, not both.") + } + } + } + + /// Adds a bit index into the circuit's [BitData]. + /// + /// _**Note:** You cannot add bits to registers once they are added._ + pub fn add_bit(&mut self) -> T { + let idx: BitType = self.bits.len().try_into().unwrap_or_else(|_| { + panic!( + "The number of {} in the circuit has exceeded the maximum capacity", + self.description + ) + }); + self.bit_info.push(None); + self.bits.push(OnceLock::new()); + idx.into() + } + + /// Retrieves a register by its index within the circuit + pub fn get_register(&self, index: u32) -> Option<&R> { + self.registry.get_index(index as usize) + } + + pub fn get_register_by_key(&self, key: &RegisterAsKey) -> Option<&R> { + self.registry.get(&key) + } + + // ======================= + // PyMethods + // ======================= + + /// Finds the native bit index of the given Python bit. + #[inline] + pub fn py_find_bit(&self, bit: &Bound) -> Option { + self.indices.get(&BitAsKey::new(bit)).copied() + } + + /// Map the provided Python bits to their native indices. + /// An error is returned if any bit is not registered. + pub fn py_map_bits<'py>( + &self, + bits: impl IntoIterator>, + ) -> PyResult> { + let v: Result, _> = bits + .into_iter() + .map(|b| { + self.indices + .get(&BitAsKey::new(&b)) + .copied() + .ok_or_else(|| { + PyKeyError::new_err(format!( + "Bit {:?} has not been added to this circuit.", + b + )) + }) + }) + .collect(); + v.map(|x| x.into_iter()) + } + + /// Gets the Python bit corresponding to the given native + /// bit index. + #[inline] + pub fn py_get_bit(&mut self, py: Python, index: T) -> PyResult> { + /* + For this method we want to make sure a couple of things are done first: + + - First off, the method needs mutable access to all of `BitData` for a couple + of reasons, but mainly because it needs to be able to initialize the `OnceCell` + for both the `Bit` instance as well as the register. + + There is a problem with this as it could cause two mutable references to `BitData`. + How do we solve this? I guess we solved it LOL + */ + let index_as_usize = BitType::from(index) as usize; + // First check if the cell is in range if not, return none + if self.bits.get(index_as_usize).is_none() { + Ok(None) + } + // If the bit has an assigned register, check if it has been initialized. + else if let Some(bit_info) = self.bit_info[index_as_usize] { + // If it is not initalized and has a register, initialize the register + // and retrieve it from there the first time + if self.bits[index_as_usize].get().is_none() { + // A register index is guaranteed to exist in the instance of `BitData`. + let py_reg = self.py_get_register(py, bit_info.register_index())?; + let res = py_reg.unwrap().bind(py).get_item(bit_info.index())?; + self.bits[index_as_usize] + .set(res.into()) + .map_err(|_| PyRuntimeError::new_err("Could not set the OnceCell correctly"))?; + return Ok(self.bits[index_as_usize].get()); + } + // If it is initialized, just retrieve. + else { + return Ok(self.bits[index_as_usize].get()); + } + } else if let Some(bit) = self.bits[index_as_usize].get() { + Ok(Some(bit)) + } else { + self.bits[index_as_usize] + .set(T::to_py_bit(py)?) + .map_err(|_| PyRuntimeError::new_err("Could not set the OnceCell correctly"))?; + Ok(self.bits[index_as_usize].get()) + } + } + + /// Retrieves a register instance from Python based on the rust description. + pub fn py_get_register(&mut self, py: Python, index: u32) -> PyResult> { + let index_as_usize = index as usize; + // First check if the cell is in range if not, return none + if self.registers.get(index_as_usize).is_none() { + Ok(None) + } else if self.registers[index_as_usize].get().is_none() { + let register = &self.registry[index as usize]; + // Decide the register type based on its key + let reg_as_key = register.as_key(); + let reg_type = match reg_as_key { + RegisterAsKey::Register(_) => REGISTER.get_bound(py), + RegisterAsKey::Quantum(_) => QUANTUM_REGISTER.get_bound(py), + RegisterAsKey::Classical(_) => CLASSICAL_REGISTER.get_bound(py), + }; + // Check if any indices have been initialized, if such is the case + // Treat the rest of indices as new `Bits`` + if register + .bits() + .any(|bit| self.bits[BitType::from(bit) as usize].get().is_some()) + { + let bits: Vec = register + .bits() + .map(|bit| -> PyResult { + if let Some(bit_obj) = self.bits[BitType::from(bit) as usize].get() { + Ok(bit_obj.clone_ref(py)) + } else { + T::to_py_bit(py) + } + }) + .collect::>()?; + + // Extract kwargs + let kwargs = PyDict::new_bound(py); + kwargs.set_item("name", register.name())?; + kwargs.set_item("bits", bits)?; + + // Create register and assign to OnceCell + let reg = reg_type.call((), Some(&kwargs))?; + self.registers[index_as_usize] + .set(reg.into()) + .map_err(|_| PyRuntimeError::new_err("Could not set the OnceCell correctly"))?; + Ok(self.registers[index_as_usize].get()) + } else { + let reg = reg_type.call1((register.len(), register.name()))?; + self.registers[index_as_usize] + .set(reg.into()) + .map_err(|_| PyRuntimeError::new_err("Could not set the OnceCell correctly"))?; + Ok(self.registers[index_as_usize].get()) + } + } else { + Ok(self.registers[index_as_usize].get()) + } + } + + /// Adds a new Python bit. + /// + /// _**Note:** If this Bit has register information, it will not be reflected unless + /// the Register is also added._ + pub fn py_add_bit(&mut self, bit: &Bound, strict: bool) -> PyResult { + let py: Python<'_> = bit.py(); + + let idx: BitType = self.bits.len().try_into().map_err(|_| { + PyRuntimeError::new_err(format!( + "The number of {} in the circuit has exceeded the maximum capacity", + self.description + )) + })?; + if self + .indices + .try_insert(BitAsKey::new(bit), idx.into()) + .is_ok() + { + self.bit_info.push(None); + self.bits.push(bit.into_py(py).into()); + // self.cached.bind(py).append(bit)?; + } else if strict { + return Err(PyValueError::new_err(format!( + "Existing bit {:?} cannot be re-added in strict mode.", + bit + ))); + } + Ok(idx.into()) + } + + pub fn py_add_register(&mut self, register: &Bound) -> PyResult { + // let index: u32 = self.registers.len().try_into().map_err(|_| { + // PyRuntimeError::new_err(format!( + // "The number of {} registers in the circuit has exceeded the maximum capacity", + // self.description + // )) + // })?; + + let bits: Vec = register + .iter()? + .map(|bit| -> PyResult { + let bit = bit?; + if let Some(idx) = self.indices.get(&BitAsKey::new(&bit)) { + Ok(*idx) + } else { + self.py_add_bit(&bit, true) + } + }) + .collect::>()?; + + let name: String = register.getattr("name")?.extract()?; + self.registers.push(register.clone().unbind().into()); + Ok(self.add_register(Some(name), None, Some(&bits))) + } + + pub fn py_remove_bit_indices(&mut self, py: Python, indices: I) -> PyResult<()> + where + I: IntoIterator, + { + let mut indices_sorted: Vec = indices + .into_iter() + .map(|i| >::from(i) as usize) + .collect(); + indices_sorted.sort(); + + for index in indices_sorted.into_iter().rev() { + // self.cached.bind(py).del_item(index)?; + let bit = self + .py_get_bit(py, (index as BitType).into())? + .unwrap() + .clone_ref(py); + self.indices.remove(&BitAsKey::new(bit.bind(py))); + self.bits.remove(index); + self.bit_info.remove(index); + } + // Update indices. + for i in 0..self.bits.len() { + let bit = self + .py_get_bit(py, (i as BitType).into())? + .unwrap() + .clone_ref(py); + self.indices + .insert(BitAsKey::new(bit.bind(py)), (i as BitType).into()); + } + Ok(()) + } + + /// Called during Python garbage collection, only!. + /// Note: INVALIDATES THIS INSTANCE. + pub fn dispose(&mut self) { + self.indices.clear(); + self.bits.clear(); + self.registers.clear(); + self.bit_info.clear(); + self.registry.clear(); + } +} diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 67ae9f85897f..b3a4aa90b55c 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -66,10 +66,12 @@ pub static CONTROL_FLOW_OP: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.controlflow", "ControlFlowOp"); pub static QUBIT: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.quantumregister", "Qubit"); pub static CLBIT: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.classicalregister", "Clbit"); +pub static BIT: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.bit", "Bit"); pub static QUANTUM_REGISTER: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.quantumregister", "QuantumRegister"); pub static CLASSICAL_REGISTER: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.classicalregister", "ClassicalRegister"); +pub static REGISTER: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.register", "Register"); pub static PARAMETER_EXPRESSION: ImportOnceCell = ImportOnceCell::new("qiskit.circuit.parameterexpression", "ParameterExpression"); pub static PARAMETER_VECTOR: ImportOnceCell = diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index a48cf2fc2f40..af83cd29a3c4 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -31,6 +31,7 @@ pub mod util; mod rustworkx_core_vnext; +use imports::{CLBIT, QUBIT}; use pyo3::prelude::*; use pyo3::types::{PySequence, PyTuple}; @@ -124,13 +125,28 @@ impl From for BitType { } } +/// **For development purposes only.** This ensures we convert to the correct Bit +/// type in Python since [BitData] does not know what its types are inherently. +pub trait ToPyBit { + /// Creates an empty bit from a rust bit instance of the correct type. + /// + /// _**Note:** Should only be used when dealing with fully opaque bits._ + fn to_py_bit(py: Python) -> PyResult; +} + +impl ToPyBit for Qubit { + fn to_py_bit(py: Python) -> PyResult { + QUBIT.get_bound(py).call0().map(|bit| bit.into()) + } +} + +impl ToPyBit for Clbit { + fn to_py_bit(py: Python) -> PyResult { + CLBIT.get_bound(py).call0().map(|bit| bit.into()) + } +} + pub fn circuit(m: &Bound) -> PyResult<()> { - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/crates/circuit/src/register.rs b/crates/circuit/src/register.rs index 0ec7903a39e6..78ad590d70ba 100644 --- a/crates/circuit/src/register.rs +++ b/crates/circuit/src/register.rs @@ -1,27 +1,17 @@ -use hashbrown::HashMap; +use hashbrown::Equivalent; use indexmap::IndexSet; -use pyo3::{ - exceptions::{PyTypeError, PyValueError}, - intern, - prelude::*, - types::{PyAnyMethods, PyList, PySet}, - FromPyObject, PyTypeInfo, -}; +use pyo3::{exceptions::PyTypeError, intern, types::PyAnyMethods, FromPyObject}; use std::{ hash::{Hash, Hasher}, - sync::{Mutex, OnceLock}, + ops::Index, + sync::Mutex, }; use crate::{ - bit::{PyBit, PyClbit, PyQubit}, - circuit_data::CircuitError, - interner::{Interned, Interner}, - slice::PySequenceIndex, + imports::{CLASSICAL_REGISTER, QUANTUM_REGISTER, REGISTER}, Clbit, Qubit, }; -static REGISTER_INSTANCE_COUNTER: Mutex = Mutex::new(0); - /// This represents the hash value of a Register according to the register's /// name and number of qubits. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -33,11 +23,11 @@ pub enum RegisterAsKey { impl RegisterAsKey { #[inline] - pub fn reduce(&self) -> (&str, u32) { + pub fn reduce(&self) -> (u32, &str) { match self { - RegisterAsKey::Register((name, num_qubits)) => (name.as_str(), *num_qubits), - RegisterAsKey::Quantum((name, num_qubits)) => (name.as_str(), *num_qubits), - RegisterAsKey::Classical((name, num_qubits)) => (name.as_str(), *num_qubits), + RegisterAsKey::Register((name, num_qubits)) => (*num_qubits, name.as_str()), + RegisterAsKey::Quantum((name, num_qubits)) => (*num_qubits, name.as_str()), + RegisterAsKey::Classical((name, num_qubits)) => (*num_qubits, name.as_str()), } } @@ -71,14 +61,14 @@ impl RegisterAsKey { impl<'py> FromPyObject<'py> for RegisterAsKey { fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult { - if ob.is_instance(&PyRegister::type_object_bound(ob.py()))? { + if ob.is_instance(REGISTER.get_bound(ob.py()))? { let (name, num_qubits) = ( ob.getattr(intern!(ob.py(), "name"))?.extract()?, ob.len()? as u32, ); - if ob.downcast::().is_ok() { + if ob.is_instance(CLASSICAL_REGISTER.get_bound(ob.py()))? { return Ok(RegisterAsKey::Classical((name, num_qubits))); - } else if ob.downcast::().is_ok() { + } else if ob.is_instance(QUANTUM_REGISTER.get_bound(ob.py()))? { return Ok(RegisterAsKey::Quantum((name, num_qubits))); } else { return Ok(RegisterAsKey::Register((name, num_qubits))); @@ -96,16 +86,22 @@ pub trait Register { /// Returns the size of the [Register]. fn len(&self) -> usize; + /// Checks if the [Register] is empty. + fn is_empty(&self) -> bool; + /// Returns the name of the [Register]. + fn name(&self) -> &str; /// Checks if a bit exists within the [Register]. fn contains(&self, bit: Self::Bit) -> bool; /// Finds the local index of a certain bit within [Register]. fn find_index(&self, bit: Self::Bit) -> Option; /// Return an iterator over all the bits in the register fn bits(&self) -> impl ExactSizeIterator; + /// Returns the register as a Key + fn as_key(&self) -> RegisterAsKey; } macro_rules! create_register { - ($name:ident, $bit:ty, $counter:ident, $prefix:literal) => { + ($name:ident, $bit:ty, $counter:ident, $prefix:literal, $key:expr) => { static $counter: Mutex = Mutex::new(0); #[derive(Debug, Clone, Eq)] @@ -115,7 +111,14 @@ macro_rules! create_register { } impl $name { - pub fn new(size: usize, name: Option) -> Self { + pub fn new(size: Option, name: Option, bits: Option<&[$bit]>) -> Self { + let register = if let Some(size) = size { + (0..size).map(|bit| <$bit>::new(bit)).collect() + } else if let Some(bits) = bits { + bits.iter().copied().collect() + } else { + panic!("You should only provide either a size or the bit indices, not both.") + }; let name = if let Some(name) = name { name } else { @@ -128,10 +131,7 @@ macro_rules! create_register { }; format!("{}{}", $prefix, count) }; - Self { - register: (0..size).map(|bit| <$bit>::new(bit)).collect(), - name, - } + Self { register, name } } } @@ -142,6 +142,14 @@ macro_rules! create_register { self.register.len() } + fn is_empty(&self) -> bool { + self.register.is_empty() + } + + fn name(&self) -> &str { + self.name.as_str() + } + fn contains(&self, bit: Self::Bit) -> bool { self.register.contains(&bit) } @@ -153,6 +161,10 @@ macro_rules! create_register { fn bits(&self) -> impl ExactSizeIterator { self.register.iter().copied() } + + fn as_key(&self) -> RegisterAsKey { + $key((self.name.clone(), self.len().try_into().unwrap())) + } } impl Hash for $name { @@ -166,413 +178,70 @@ macro_rules! create_register { self.register.len() == other.register.len() && self.name == other.name } } - }; -} - -create_register!(QuantumRegister, Qubit, QREG_COUNTER, "qr"); -create_register!(ClassicalRegister, Clbit, CREG_COUNTER, "cr"); - -/// Represents a collection of registers of a certain type within a circuit. -#[derive(Debug, Clone)] -pub(crate) struct CircuitRegistry { - registry: Interner, - /// Python cache for registers - python_cache: HashMap, OnceLock>>, -} - -impl CircuitRegistry { - pub fn add_register(&mut self, register: T) -> Interned { - self.registry.insert_owned(register) - } - - /// Retreives the index of a register if it exists within a registry. - pub fn find_index(&self, register: &T) -> Option> { - self.registry.get_interned(register) - } - /// Checks if a register exists within a circuit - pub fn contains(&self, register: &T) -> bool { - self.registry.contains(register) - } -} - -#[derive(FromPyObject)] -enum SliceOrInt<'py> { - Slice(PySequenceIndex<'py>), - List(Vec), -} + impl Index for $name { + type Output = $bit; -/// Python representation of a generic register -#[derive(Debug, Clone)] -#[pyclass(name = "Register", module = "qiskit.circuit.register", subclass)] -pub struct PyRegister { - /// Bits are stored in Python-space. - bits: Vec>, - /// Name of the register in question - #[pyo3(get)] - name: String, - /// Size of the register - #[pyo3(get)] - size: u32, - /// Mapping of the hash value of each bit and their index in the register. - bit_indices: HashMap, -} - -#[pymethods] -impl PyRegister { - #[new] - pub fn new( - py: Python, - mut size: Option, - mut name: Option, - bits: Option>>, - ) -> PyResult { - if (size.is_none(), bits.is_none()) == (false, false) || (size.is_some() && bits.is_some()) - { - return Err( - CircuitError::new_err( - format!("Exactly one of the size or bits arguments can be provided. Provided size={:?} bits={:?}.", size, bits) - ) - ); - } - if let Some(bits) = bits.as_ref() { - size = Some(bits.len() as u32); - } - if name.is_none() { - let count = if let Ok(ref mut count) = REGISTER_INSTANCE_COUNTER.try_lock() { - let curr = **count; - **count += 1; - curr - } else { - panic!("Could not access register counter.") - }; - name = Some(format!("{}{}", "reg", count)); - } - if let Some(bits) = bits { - if size != Some(PySet::new_bound(py, bits.iter())?.len() as u32) { - return Err(CircuitError::new_err(format!( - "Register bits must not be duplicated. bits={:?}", - bits - ))); + fn index(&self, index: usize) -> &Self::Output { + self.register.index(index) } - let bit_indices: HashMap = bits - .iter() - .enumerate() - .flat_map(|(idx, obj)| -> PyResult<(isize, u32)> { - Ok((obj.bind(py).hash()?, idx as u32)) - }) - .collect(); - Ok(Self { - bits, - name: name.unwrap(), - size: size.unwrap(), - bit_indices, - }) - } else { - let name = name.unwrap(); - let size = size.unwrap(); - let bits: Vec> = (0..size) - .map(|idx| { - Py::new( - py, - PyBit::new( - Some(RegisterAsKey::Register((name.clone(), size))), - Some(idx), - ) - .unwrap(), - ) - .unwrap() - }) - .collect(); - let bit_indices: HashMap = bits - .iter() - .enumerate() - .flat_map(|(idx, obj)| -> PyResult<(isize, u32)> { - Ok((obj.bind(py).hash()?, idx as u32)) - }) - .collect(); - Ok(Self { - bits, - name, - size, - bit_indices, - }) } - } - - fn __repr__(slf: Bound) -> PyResult { - let borrowed = slf.borrow(); - Ok(format!( - "{}({}, '{}')", - slf.get_type().name()?, - borrowed.size, - borrowed.name, - )) - } - - fn __len__(&self) -> usize { - self.size as usize - } - fn __getitem__(&self, py: Python, key: SliceOrInt) -> PyResult { - match key { - SliceOrInt::Slice(py_sequence_index) => { - let sequence = py_sequence_index.with_len(self.size.try_into().unwrap())?; - match sequence { - crate::slice::SequenceIndex::Int(idx) => { - Ok(self.bits[idx].clone_ref(py).into_any()) - } - _ => Ok(PyList::new_bound( - py, - sequence.iter().map(|idx| self.bits[idx].clone_ref(py)), - ) - .into()), - } - } - SliceOrInt::List(vec) => { - if vec.iter().max() < Some(&(self.size as usize)) { - Ok( - PyList::new_bound(py, vec.iter().map(|idx| self.bits[*idx].clone_ref(py))) - .into(), - ) - } else { - Err(CircuitError::new_err("Register index is our of range")) - } + impl From<(usize, Option)> for $name { + fn from(value: (usize, Option)) -> Self { + Self::new(Some(value.0), value.1, None) } } - } - - fn __contains__(&self, bit: &Bound) -> PyResult { - Ok(self.bit_indices.contains_key(&bit.hash()?)) - } - fn index(slf: Bound, bit: &Bound) -> PyResult { - let borrowed = slf.borrow(); - if borrowed.__contains__(bit)? { - Ok(borrowed.bit_indices[&bit.hash()?]) - } else { - Err(PyValueError::new_err(format!( - "Bit {} not found in Register {}.", - bit.repr()?, - slf.repr()?, - ))) + impl From<&[$bit]> for $name { + fn from(value: &[$bit]) -> Self { + Self::new(None, None, Some(value)) + } } - } - - fn __iter__<'py>(&'py self, py: Python<'py>) -> PyResult> { - PyList::new_bound(py, self.bits.iter().map(|obj| obj.clone_ref(py))) - .into_any() - .iter() - } - fn __getnewargs__(&self, py: Python) -> (Option, String, PyObject) { - ( - None, - self.name.clone(), - self.bits - .iter() - .map(|bit| bit.clone_ref(py)) - .collect::>() - .into_py(py), - ) - } - - fn __getstate__(&self, py: Python) -> (String, u32, PyObject) { - ( - self.name.clone(), - self.size, - self.bits - .iter() - .map(|bit| bit.clone_ref(py)) - .collect::>() - .into_py(py), - ) - } - - fn __setstate__(&mut self, py: Python, state: (String, u32, PyObject)) -> PyResult<()> { - self.name = state.0; - self.size = state.1; - self.bits = state.2.extract(py)?; - self.bit_indices = self - .bits - .iter() - .enumerate() - .flat_map(|(idx, obj)| -> PyResult<(isize, u32)> { - Ok((obj.bind(py).hash()?, idx as u32)) - }) - .collect(); - Ok(()) - } - - fn __eq__(slf: Bound, other: Bound) -> PyResult { - if slf.is(&other) { - return Ok(true); + impl From<(&[$bit], String)> for $name { + fn from(value: (&[$bit], String)) -> Self { + Self::new(None, Some(value.1), Some(value.0)) + } } - - let self_borrowed = slf.borrow(); - let other_borrowed = other.borrow(); - - Ok(slf.get_type().eq(other.get_type())? - && slf.repr()?.to_string() == other.repr()?.to_string() - && self_borrowed - .bits - .iter() - .zip(other_borrowed.bits.iter()) - .filter_map(|(bit, other)| -> Option { - let borrowed_bit = bit.borrow(slf.py()); - let borrowed_other = other.borrow(slf.py()); - match (borrowed_bit.is_new(), borrowed_other.is_new()) { - (false, false) => None, - _ => Some(bit.is(other)), - } - }) - .all(|bool| bool)) - } + }; } -#[derive(Debug, Clone)] -#[pyclass(name="QuantumRegister", module="qiskit.circuit.quantumregister", extends=PyRegister)] -pub struct PyQuantumRegister(); - -#[pymethods] -impl PyQuantumRegister { - #[new] - pub fn new( - py: Python, - mut size: Option, - mut name: Option, - mut bits: Option>>, - ) -> PyResult<(Self, PyRegister)> { - if (size.is_none(), bits.is_none()) == (false, false) || (size.is_some() && bits.is_some()) - { - return Err( - CircuitError::new_err( - format!("Exactly one of the size or bits arguments can be provided. Provided size={:?} bits={:?}.", size, bits) - ) - ); - } - if name.is_none() { - // This line is the reason we cannot turn this into a macro-rule - let count = if let Ok(ref mut count) = QREG_COUNTER.try_lock() { - let curr = **count; - **count += 1; - curr - } else { - panic!("Could not access register counter.") - }; - name = Some(format!("{}{}", "q", count)); - } - if bits.is_none() && size.is_some() { - bits = Some( - (0..size.unwrap()) - .map(|idx| { - Py::new( - py, - PyQubit::py_new( - Some(RegisterAsKey::Quantum(( - name.clone().unwrap(), - size.unwrap(), - ))), - Some(idx), - ) - .unwrap(), - ) - .unwrap() - }) - .collect(), - ); - size = None; +create_register!( + QuantumRegister, + Qubit, + QREG_COUNTER, + "qr", + RegisterAsKey::Quantum +); + +create_register!( + ClassicalRegister, + Clbit, + CREG_COUNTER, + "cr", + RegisterAsKey::Classical +); + +// Add equivalencies between Keys and Registers +impl Equivalent for RegisterAsKey { + fn equivalent(&self, key: &QuantumRegister) -> bool { + match self { + Self::Quantum((name, length)) => { + name == &key.name && *length == key.len().try_into().unwrap() + } + _ => false, } - Ok(( - Self(), - PyRegister::new( - py, - size, - name, - bits.map(|vec| { - vec.into_iter() - .map(|ob| { - as Clone>::clone(&ob.into_bound(py)) - .downcast_into() - .unwrap() - .into() - }) - .collect() - }), - )?, - )) } } -#[derive(Debug, Clone)] -#[pyclass(name="ClassicalRegister", module="qiskit.circuit.classicalregister", extends=PyRegister)] -pub struct PyClassicalRegister(); - -#[pymethods] -impl PyClassicalRegister { - #[new] - pub fn new( - py: Python, - mut size: Option, - mut name: Option, - mut bits: Option>>, - ) -> PyResult<(Self, PyRegister)> { - if (size.is_none(), bits.is_none()) == (false, false) || (size.is_some() && bits.is_some()) - { - return Err( - CircuitError::new_err( - format!("Exactly one of the size or bits arguments can be provided. Provided size={:?} bits={:?}.", size, bits) - ) - ); - } - if bits.is_none() && size.is_some() { - bits = Some( - (0..size.unwrap()) - .map(|idx| { - Py::new( - py, - PyClbit::py_new( - Some(RegisterAsKey::Quantum(( - name.clone().unwrap(), - size.unwrap(), - ))), - Some(idx), - ) - .unwrap(), - ) - .unwrap() - }) - .collect(), - ); - size = None; - } - if name.is_none() { - let count = if let Ok(ref mut count) = CREG_COUNTER.try_lock() { - let curr = **count; - **count += 1; - curr - } else { - panic!("Could not access register counter.") - }; - name = Some(format!("{}{}", "c", count)); +impl Equivalent for RegisterAsKey { + fn equivalent(&self, key: &ClassicalRegister) -> bool { + match self { + Self::Classical((name, length)) => { + name == &key.name && *length == key.len().try_into().unwrap() + } + _ => false, } - Ok(( - Self(), - PyRegister::new( - py, - size, - name, - bits.map(|vec| { - vec.into_iter() - .map(|ob| { - as Clone>::clone(&ob.into_bound(py)) - .downcast_into() - .unwrap() - .into() - }) - .collect() - }), - )?, - )) } }