diff --git a/crates/circuit/src/bit.rs b/crates/circuit/src/bit.rs new file mode 100644 index 000000000000..b749e8a2cd42 --- /dev/null +++ b/crates/circuit/src/bit.rs @@ -0,0 +1,65 @@ +use std::fmt::Debug; + +/// Keeps informatyion about where a bit is located within the circuit. +/// +/// This information includes whether the bit was added by a register, +/// which register it belongs to and where it is located within it. +#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub struct BitInfo { + added_by_reg: bool, + registers: Vec, +} + +impl BitInfo { + pub fn new(orig_reg: Option<(u32, u32)>) -> Self { + // If the instance was added by a register, add it and prefil its locator + if let Some((reg_idx, idx)) = orig_reg { + Self { + added_by_reg: true, + registers: vec![BitLocation::new(reg_idx, idx)], + } + } else { + Self { + added_by_reg: false, + registers: vec![], + } + } + } + + /// Add a register to the bit instance + pub fn add_register(&mut self, register: u32, index: u32) { + self.registers.push(BitLocation(register, index)) + } + + /// Returns a list with all the [BitLocation] instances + pub fn get_registers(&self) -> &[BitLocation] { + &self.registers + } + + /// Returns the index of the original register if any exists + pub fn orig_register_index(&self) -> Option<&BitLocation> { + if self.added_by_reg { + Some(&self.registers[0]) + } else { + None + } + } +} + +/// Keeps information about where a qubit is located within a register. +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Ord, Eq, Hash)] +pub struct BitLocation(u32, u32); + +impl BitLocation { + pub fn new(register_idx: u32, index: u32) -> Self { + Self(register_idx, index) + } + + pub fn register_index(&self) -> u32 { + self.0 + } + + pub fn index(&self) -> u32 { + self.1 + } +} diff --git a/crates/circuit/src/bit_data.rs b/crates/circuit/src/bit_data.rs index bf9667e44214..ae0f52b4b711 100644 --- a/crates/circuit/src/bit_data.rs +++ b/crates/circuit/src/bit_data.rs @@ -10,13 +10,18 @@ // 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, BitLocation}; +use crate::circuit_data::CircuitError; +use crate::imports::{CLASSICAL_REGISTER, QUANTUM_REGISTER, REGISTER}; +use crate::register::{Register, RegisterAsKey}; +use crate::{BitType, ToPyBit}; use hashbrown::HashMap; use pyo3::exceptions::{PyKeyError, PyRuntimeError, PyValueError}; use pyo3::prelude::*; -use pyo3::types::PyList; +use pyo3::types::{PyDict, PyList}; 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 +34,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 +235,602 @@ 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, + /// Maps Register keys to indices + reg_keys: HashMap, + /// Mapping between bit index and its register info + bit_info: Vec, + /// Registers in the circuit + registry: Vec, + /// Registers in Python + registers: Vec>, + /// Cached Python bits + cached_py_bits: OnceLock>, + /// Cached Python registers + cached_py_regs: OnceLock>, +} + +impl NewBitData +where + T: From + Copy + Debug + ToPyBit, + R: Register + + Hash + + Eq + + From<(usize, Option)> + + for<'a> From<&'a [T]> + + for<'a> From<(&'a [T], String)>, + BitType: From, +{ + pub fn new(description: String) -> Self { + NewBitData { + description, + bits: Vec::new(), + indices: HashMap::new(), + bit_info: Vec::new(), + registry: Vec::new(), + registers: Vec::new(), + cached_py_bits: OnceLock::new(), + cached_py_regs: OnceLock::new(), + reg_keys: HashMap::new(), + } + } + + pub fn with_capacity(description: String, bit_capacity: usize, reg_capacity: usize) -> Self { + NewBitData { + description, + bits: Vec::with_capacity(bit_capacity), + indices: HashMap::with_capacity(bit_capacity), + bit_info: Vec::with_capacity(bit_capacity), + registry: Vec::with_capacity(reg_capacity), + registers: Vec::with_capacity(reg_capacity), + cached_py_bits: OnceLock::new(), + cached_py_regs: OnceLock::new(), + reg_keys: HashMap::with_capacity(reg_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() + } + + /// Adds a register onto the [BitData] of the circuit. + /// + /// _**Note:** If providing the ``bits`` argument, the bits must exist in the circuit._ + pub fn add_register( + &mut self, + name: Option, + size: Option, + bits: Option<&[T]>, + ) -> u32 { + let idx = self.registry.len().try_into().unwrap_or_else(|_| { + panic!( + "The {} registry in this circuit has reached its maximum capacity.", + self.description + ) + }); + match (size, bits) { + (None, None) => panic!("You should at least provide either a size or the bit indices."), + (None, Some(bits)) => { + let reg: R = if let Some(name) = name { + (bits, name).into() + } else { + bits.into() + }; + // 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]; + bit_info.add_register( + idx, + bit_idx.try_into().unwrap_or_else(|_| { + panic!( + "The current register exceeds its capacity limit. Number of {} : {}", + self.description, + reg.len() + ) + }), + ); + } + self.reg_keys.insert(reg.as_key().clone(), idx); + self.registry.push(reg); + self.registers.push(OnceLock::new()); + idx + } + (Some(size), None) => { + let bits: Vec = (0..size) + .map(|bit| { + self.add_bit_inner(Some(( + idx, + bit.try_into().unwrap_or_else(|_| { + panic!( + "The current register exceeds its capacity limit. Number of {} : {}", + self.description, + size + ) + }), + ))) + }) + .collect(); + let reg: R = if let Some(name) = name { + (bits.as_slice(), name).into() + } else { + bits.as_slice().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.reg_keys.insert(reg.as_key().clone(), idx); + self.registry.push(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 { + self.add_bit_inner(None) + } + + fn add_bit_inner(&mut self, reg: Option<(u32, u32)>) -> 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(BitInfo::new(reg)); + self.bits.push(OnceLock::new()); + idx.into() + } + + /// Retrieves the register info of a bit. Will panic if the index is out of range. + pub fn get_bit_info(&self, index: T) -> &[BitLocation] { + self.bit_info[BitType::from(index) as usize].get_registers() + } + + /// Retrieves a register by its index within the circuit + #[inline] + pub fn get_register(&self, index: u32) -> Option<&R> { + self.registry.get(index as usize) + } + + #[inline] + pub fn get_register_by_key(&self, key: &RegisterAsKey) -> Option<&R> { + self.reg_keys + .get(key) + .and_then(|idx| self.get_register(*idx)) + } + + /// Checks if a register is in the circuit + #[inline] + pub fn contains_register(&self, reg: &R) -> bool { + self.contains_register_by_key(reg.as_key()) + } + + #[inline] + pub fn contains_register_by_key(&self, reg: &RegisterAsKey) -> bool { + self.reg_keys.contains_key(reg) + } +} + +// PyMethods +impl NewBitData +where + T: From + Copy + Debug + ToPyBit, + R: Register + + Hash + + Eq + + From<(usize, Option)> + + for<'a> From<&'a [T]> + + for<'a> From<(&'a [T], String)>, + BitType: From, +{ + /// 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() + } + + /// Gets a reference to the cached Python list, with the bits maintained by + /// this instance. + #[inline] + pub fn py_cached_bits(&self, py: Python) -> &Py { + self.cached_py_bits.get_or_init(|| { + PyList::new( + py, + (0..self.len()).map(|idx| self.py_get_bit(py, (idx as u32).into()).unwrap()), + ) + .unwrap() + .into() + }) + } + + /// Gets a reference to the cached Python list, with the registers maintained by + /// this instance. + #[inline] + pub fn py_cached_regs(&self, py: Python) -> &Py { + self.cached_py_regs.get_or_init(|| { + PyList::new( + py, + (0..self.len_regs()).map(|idx| self.py_get_register(py, idx as u32).unwrap()), + ) + .unwrap() + .into() + }) + } + + /// Gets a reference to the underlying vector of Python bits. + #[inline] + pub fn py_bits(&self, py: Python) -> PyResult> { + (0..self.len()) + .map(|idx| { + self.py_get_bit(py, (idx as u32).into()) + .map(|bit| bit.unwrap()) + }) + .collect::>() + } + + /// Gets the location of a bit within the circuit + pub fn py_get_bit_location(&self, bit: &Bound) -> PyResult> { + let py = bit.py(); + let index = self.py_find_bit(bit).ok_or(PyKeyError::new_err(format!( + "The provided {} is not part of this circuit", + self.description + )))?; + self.get_bit_info(index) + .iter() + .map(|info| -> PyResult<(u32, &PyObject)> { + Ok(( + info.index(), + self.py_get_register(py, info.register_index())?.unwrap(), + )) + }) + .collect::>>() + } + + /// Gets a reference to the underlying vector of Python registers. + #[inline] + pub fn py_registers(&self, py: Python) -> PyResult> { + (0..self.len_regs() as u32) + .map(|idx| self.py_get_register(py, idx).map(|reg| reg.unwrap())) + .collect::>() + } + + /// 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()) + } + + /// Map the provided native indices to the corresponding Python + /// bit instances. + /// Panics if any of the indices are out of range. + pub fn py_map_indices( + &self, + py: Python, + bits: &[T], + ) -> PyResult>> { + let v: Vec<_> = bits + .iter() + .map(|i| -> PyResult<&PyObject> { Ok(self.py_get_bit(py, *i)?.unwrap()) }) + .collect::>()?; + Ok(v.into_iter()) + } + + /// Gets the Python bit corresponding to the given native + /// bit index. + #[inline] + pub fn py_get_bit(&self, py: Python, index: T) -> PyResult> { + 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].orig_register_index() { + // If it is not initalized and has a register, initialize the original 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(&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(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(); + + if self.bits.len() != self.py_cached_bits(py).bind(bit.py()).len() { + return Err(PyRuntimeError::new_err( + format!("This circuit's {} list has become out of sync with the circuit data. Did something modify it?", self.description) + )); + } + + 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.py_cached_bits(py).bind(py).append(bit)?; + self.bit_info.push(BitInfo::new(None)); + self.bits.push(bit.clone().unbind().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()) + } + + /// Adds new register from Python. + pub fn py_add_register(&mut self, register: &Bound) -> PyResult { + let py = register.py(); + if self.registers.len() != self.py_cached_regs(py).bind(py).len() { + return Err(PyRuntimeError::new_err( + format!("This circuit's {} register list has become out of sync with the circuit data. Did something modify it?", self.description) + )); + } + let key: RegisterAsKey = register.extract()?; + if self.reg_keys.contains_key(&key) { + return Err(CircuitError::new_err(format!( + "A {} register of name {} already exists in the circuit", + &self.description, + key.name() + ))); + } + + let idx: 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 + .try_iter()? + .enumerate() + .map(|(bit_index, bit)| -> PyResult { + let bit_index: u32 = bit_index.try_into().map_err(|_| { + CircuitError::new_err(format!( + "The current register exceeds its capacity limit. Number of {} : {}", + self.description, + key.size() + )) + })?; + let bit = bit?; + let index = if let Some(idx) = self.indices.get(&BitAsKey::new(&bit)) { + *idx + } else { + self.py_add_bit(&bit, true)? + }; + self.bit_info[BitType::from(index) as usize].add_register(idx, bit_index); + Ok(index) + }) + .collect::>()?; + + let name: String = key.name().to_string(); + self.py_cached_regs(py).bind(py).append(register)?; + let idx = self.add_register(Some(name), None, Some(&bits)); + self.registers[idx as usize] = register.clone().unbind().into(); + Ok(idx) + } + + /// Works as a setter for Python registers when the circuit needs to discard old data. + /// This method discards the current registers and the data associated with them from its + /// respective bits. + pub fn py_set_registers(&mut self, other: &Bound) -> PyResult<()> { + // First invalidate everything related to registers + // This is done to ensure we regenerate the lost information + // self.bit_info.clear() + + self.reg_keys.clear(); + self.registers.clear(); + self.registry.clear(); + self.cached_py_regs.take(); + + // Re-assign + for reg in other.iter() { + self.py_add_register(®)?; + } + + Ok(()) + } + + 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.py_cached_bits(py).bind(py).del_item(index)?; + let bit = self.py_get_bit(py, (index as BitType).into())?.unwrap(); + 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(); + self.indices + .insert(BitAsKey::new(bit.bind(py)), (i as BitType).into()); + } + Ok(()) + } + + pub(crate) fn py_bits_raw(&self) -> &[OnceLock] { + &self.bits + } + + pub(crate) fn py_bits_cached_raw(&self) -> Option<&Py> { + self.cached_py_bits.get() + } + + pub(crate) fn py_regs_raw(&self) -> &[OnceLock] { + &self.bits + } + + pub(crate) fn py_regs_cached_raw(&self) -> Option<&Py> { + self.cached_py_bits.get() + } + + /// 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(); + } + + /// To convert [BitData] into [NewBitData]. If the structure the original comes from contains register + /// info. Make sure to add it manually after. + pub fn from_bit_data(py: Python, bit_data: &BitData) -> Self { + Self { + description: bit_data.description.clone(), + bits: bit_data + .bits + .iter() + .map(|bit| bit.clone_ref(py).into()) + .collect(), + indices: bit_data.indices.clone(), + reg_keys: HashMap::new(), + bit_info: (0..bit_data.len()).map(|_| BitInfo::new(None)).collect(), + registry: Vec::new(), + registers: Vec::new(), + cached_py_bits: OnceLock::new(), + cached_py_regs: OnceLock::new(), + } + } +} diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index be36aed73f6b..a8d47f95a954 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -13,7 +13,8 @@ #[cfg(feature = "cache_pygates")] use std::sync::OnceLock; -use crate::bit_data::BitData; +use crate::bit::BitLocation; +use crate::bit_data::NewBitData; use crate::circuit_instruction::{ CircuitInstruction, ExtraInstructionAttributes, OperationFromPython, }; @@ -23,6 +24,7 @@ use crate::interner::{Interned, Interner}; use crate::operations::{Operation, OperationRef, Param, StandardGate}; use crate::packed_instruction::{PackedInstruction, PackedOperation}; use crate::parameter_table::{ParameterTable, ParameterTableError, ParameterUse, ParameterUuid}; +use crate::register::{ClassicalRegister, QuantumRegister}; use crate::slice::{PySequenceIndex, SequenceIndex}; use crate::{Clbit, Qubit}; @@ -102,9 +104,9 @@ pub struct CircuitData { /// The cache used to intern instruction bits. cargs_interner: Interner<[Clbit]>, /// Qubits registered in the circuit. - qubits: BitData, + qubits: NewBitData, /// Clbits registered in the circuit. - clbits: BitData, + clbits: NewBitData, param_table: ParameterTable, #[pyo3(get)] global_phase: Param, @@ -114,7 +116,7 @@ pub struct CircuitData { impl CircuitData { #[new] #[pyo3(signature = (qubits=None, clbits=None, data=None, reserve=0, global_phase=Param::Float(0.0)))] - pub fn new( + pub fn py_new( py: Python<'_>, qubits: Option<&Bound>, clbits: Option<&Bound>, @@ -126,20 +128,20 @@ impl CircuitData { data: Vec::new(), qargs_interner: Interner::new(), cargs_interner: Interner::new(), - qubits: BitData::new(py, "qubits".to_string()), - clbits: BitData::new(py, "clbits".to_string()), + qubits: NewBitData::new("qubits".to_string()), + clbits: NewBitData::new("clbits".to_string()), param_table: ParameterTable::new(), global_phase: Param::Float(0.), }; self_.set_global_phase(py, global_phase)?; if let Some(qubits) = qubits { for bit in qubits.try_iter()? { - self_.add_qubit(py, &bit?, true)?; + self_.py_add_qubit(&bit?, true)?; } } if let Some(clbits) = clbits { for bit in clbits.try_iter()? { - self_.add_clbit(py, &bit?, true)?; + self_.py_add_clbit(&bit?, true)?; } } if let Some(data) = data { @@ -149,19 +151,68 @@ impl CircuitData { Ok(self_) } - pub fn __reduce__(self_: &Bound, py: Python<'_>) -> PyResult { + pub fn __reduce__(self_: &Bound) -> PyResult { + let py = self_.py(); let ty: Bound = self_.get_type(); let args = { let self_ = self_.borrow(); ( - self_.qubits.cached().clone_ref(py), - self_.clbits.cached().clone_ref(py), + self_.qubits.py_cached_bits(py).clone_ref(py), + self_.clbits.py_cached_bits(py).clone_ref(py), None::<()>, self_.data.len(), self_.global_phase.clone(), ) }; - (ty, args, None::<()>, self_.try_iter()?).into_py_any(py) + let state = { + let borrowed = self_.borrow(); + ( + borrowed.qubits.py_cached_regs(py).clone_ref(py), + borrowed.clbits.py_cached_regs(py).clone_ref(py), + ) + }; + (ty, args, state, self_.try_iter()?).into_py_any(py) + } + + pub fn __setstate__( + self_: &Bound, + state: (Bound, Bound), + ) -> PyResult<()> { + let mut borrowed_mut = self_.borrow_mut(); + for qreg in state.0.iter() { + borrowed_mut.py_add_qreg(&qreg)?; + } + for creg in state.1.iter() { + borrowed_mut.py_add_creg(&creg)?; + } + Ok(()) + } + + /// Returns the current mapping of registered :class:`.QuantumRegisters` names and instances + /// as a dict. + /// + /// .. warning:: + /// + /// Do not modify this list yourself. It will invalidate the :class:`CircuitData` data + /// structures. + /// + /// Returns: + /// dict(:class:`.QuantumRegister`): The current sequence of registered qubits. + #[getter("qregs")] + pub fn py_qregs(&self, py: Python<'_>) -> &Py { + self.qubits.py_cached_regs(py) + } + + /// Setter for registers, in case of forced updates. + #[setter("qregs")] + pub fn py_qreg_set(&mut self, other: &Bound) -> PyResult<()> { + self.qubits.py_set_registers(other) + } + + /// Gets the location of the bit inside of the circuit + #[pyo3(name = "get_qubit_location")] + pub fn py_get_qubit_location(&self, bit: &Bound) -> PyResult> { + self.qubits.py_get_bit_location(bit) } /// Returns the current sequence of registered :class:`.Qubit` instances as a list. @@ -175,7 +226,7 @@ impl CircuitData { /// list(:class:`.Qubit`): The current sequence of registered qubits. #[getter("qubits")] pub fn py_qubits(&self, py: Python<'_>) -> Py { - self.qubits.cached().clone_ref(py) + self.qubits.py_cached_bits(py).clone_ref(py) } /// Return the number of qubits. This is equivalent to the length of the list returned by @@ -188,6 +239,33 @@ impl CircuitData { self.qubits.len() } + /// Returns the current mapping of registered :class:`.QuantumRegisters` names and instances + /// as a dict. + /// + /// .. warning:: + /// + /// Do not modify this list yourself. It will invalidate the :class:`CircuitData` data + /// structures. + /// + /// Returns: + /// dict(:class:`.QuantumRegister`): The current sequence of registered qubits. + #[getter("cregs")] + pub fn py_cregs(&self, py: Python<'_>) -> &Py { + self.clbits.py_cached_regs(py) + } + + /// Setter for registers, in case of forced updates. + #[setter("cregs")] + pub fn py_creg_set(&mut self, other: &Bound) -> PyResult<()> { + self.clbits.py_set_registers(other) + } + + /// Gets the location of the bit inside of the circuit + #[pyo3(name = "get_clbit_location")] + pub fn py_get_clbit_location(&self, bit: &Bound) -> PyResult> { + self.qubits.py_get_bit_location(bit) + } + /// Returns the current sequence of registered :class:`.Clbit` /// instances as a list. /// @@ -199,8 +277,8 @@ impl CircuitData { /// Returns: /// list(:class:`.Clbit`): The current sequence of registered clbits. #[getter("clbits")] - pub fn py_clbits(&self, py: Python<'_>) -> Py { - self.clbits.cached().clone_ref(py) + pub fn py_clbits(&self, py: Python<'_>) -> &Py { + self.clbits.py_cached_bits(py) } /// Return the number of clbits. This is equivalent to the length of the list returned by @@ -257,9 +335,19 @@ impl CircuitData { /// Raises: /// ValueError: The specified ``bit`` is already present and flag ``strict`` /// was provided. - #[pyo3(signature = (bit, *, strict=true))] - pub fn add_qubit(&mut self, py: Python, bit: &Bound, strict: bool) -> PyResult<()> { - self.qubits.add(py, bit, strict)?; + #[pyo3(name="add_qubit", signature = (bit, *, strict=true))] + pub fn py_add_qubit(&mut self, bit: &Bound, strict: bool) -> PyResult<()> { + self.qubits.py_add_bit(bit, strict)?; + Ok(()) + } + + /// Registers a :class:`.QuantumRegister` instance. + /// + /// Args: + /// bit (:class:`.QuantumRegister`): The register to add. + #[pyo3(name="add_qreg", signature = (register, *,))] + pub fn py_add_qreg(&mut self, register: &Bound) -> PyResult<()> { + self.qubits.py_add_register(register)?; Ok(()) } @@ -272,9 +360,19 @@ impl CircuitData { /// Raises: /// ValueError: The specified ``bit`` is already present and flag ``strict`` /// was provided. - #[pyo3(signature = (bit, *, strict=true))] - pub fn add_clbit(&mut self, py: Python, bit: &Bound, strict: bool) -> PyResult<()> { - self.clbits.add(py, bit, strict)?; + #[pyo3(name="add_clbit", signature = (bit, *, strict=true))] + pub fn py_add_clbit(&mut self, bit: &Bound, strict: bool) -> PyResult<()> { + self.clbits.py_add_bit(bit, strict)?; + Ok(()) + } + + /// Registers a :class:`.ClassicalRegister` instance. + /// + /// Args: + /// bit (:class:`.ClassicalRegister`): The register to add. + #[pyo3(name="add_creg", signature = (register, *,))] + pub fn py_add_creg(&mut self, register: &Bound) -> PyResult<()> { + self.clbits.py_add_register(register)?; Ok(()) } @@ -284,10 +382,10 @@ impl CircuitData { /// CircuitData: The shallow copy. #[pyo3(signature = (copy_instructions=true, deepcopy=false))] pub fn copy(&self, py: Python<'_>, copy_instructions: bool, deepcopy: bool) -> PyResult { - let mut res = CircuitData::new( + let mut res = CircuitData::py_new( py, - Some(self.qubits.cached().bind(py)), - Some(self.clbits.cached().bind(py)), + Some(self.qubits.py_cached_bits(py).bind(py)), + Some(self.clbits.py_cached_bits(py).bind(py)), None, self.data.len(), self.global_phase.clone(), @@ -296,6 +394,13 @@ impl CircuitData { res.cargs_interner = self.cargs_interner.clone(); res.param_table.clone_from(&self.param_table); + for qreg in self.py_qregs(py).bind(py).iter() { + res.py_add_qreg(&qreg)?; + } + for creg in self.py_cregs(py).bind(py).iter() { + res.py_add_creg(&creg)?; + } + if deepcopy { let memo = PyDict::new(py); for inst in &self.data { @@ -347,10 +452,10 @@ impl CircuitData { let clbits = PySet::empty(py)?; for inst in self.data.iter() { for b in self.qargs_interner.get(inst.qubits) { - qubits.add(self.qubits.get(*b).unwrap().clone_ref(py))?; + qubits.add(self.qubits.py_get_bit(py, *b)?.unwrap().clone_ref(py))?; } for b in self.cargs_interner.get(inst.clbits) { - clbits.add(self.clbits.get(*b).unwrap().clone_ref(py))?; + clbits.add(self.clbits.py_get_bit(py, *b)?.unwrap().clone_ref(py))?; } } @@ -466,14 +571,28 @@ impl CircuitData { /// CircuitInstruction(XGate(), [qr[1]], []), /// CircuitInstruction(XGate(), [qr[0]], []), /// ]) - #[pyo3(signature = (qubits=None, clbits=None))] + #[pyo3(signature = (qubits=None, clbits=None, qregs=None, cregs=None))] pub fn replace_bits( &mut self, py: Python<'_>, qubits: Option<&Bound>, clbits: Option<&Bound>, + qregs: Option<&Bound>, + cregs: Option<&Bound>, ) -> PyResult<()> { - let mut temp = CircuitData::new(py, qubits, clbits, None, 0, self.global_phase.clone())?; + let mut temp = CircuitData::py_new(py, qubits, clbits, None, 0, self.global_phase.clone())?; + // Add qregs if provided. + if let Some(qregs) = qregs { + for qreg in qregs.try_iter()? { + temp.py_add_qreg(&qreg?)?; + } + } + // Add cregs if provided. + if let Some(cregs) = cregs { + for creg in cregs.try_iter()? { + temp.py_add_creg(&creg?)?; + } + } if qubits.is_some() { if temp.num_qubits() < self.num_qubits() { return Err(PyValueError::new_err(format!( @@ -504,16 +623,16 @@ impl CircuitData { // Note: we also rely on this to make us iterable! pub fn __getitem__(&self, py: Python, index: PySequenceIndex) -> PyResult { // Get a single item, assuming the index is validated as in bounds. - let get_single = |index: usize| { + let get_single = |index: usize| -> PyObject { let inst = &self.data[index]; let qubits = self.qargs_interner.get(inst.qubits); let clbits = self.cargs_interner.get(inst.clbits); CircuitInstruction { operation: inst.op.clone(), - qubits: PyTuple::new(py, self.qubits.map_indices(qubits)) + qubits: PyTuple::new(py, self.qubits.py_map_indices(py, qubits).unwrap()) .unwrap() .unbind(), - clbits: PyTuple::new(py, self.clbits.map_indices(clbits)) + clbits: PyTuple::new(py, self.clbits.py_map_indices(py, clbits).unwrap()) .unwrap() .unbind(), params: inst.params_view().iter().cloned().collect(), @@ -663,7 +782,7 @@ impl CircuitData { pub fn extend(&mut self, py: Python<'_>, itr: &Bound) -> PyResult<()> { if let Ok(other) = itr.downcast::() { - let other = other.borrow(); + let other = other.borrow_mut(); // Fast path to avoid unnecessary construction of CircuitInstruction instances. self.data.reserve(other.data.len()); for inst in other.data.iter() { @@ -674,7 +793,7 @@ impl CircuitData { .map(|b| { Ok(self .qubits - .find(other.qubits.get(*b).unwrap().bind(py)) + .py_find_bit(other.qubits.py_get_bit(py, *b)?.unwrap().bind(py)) .unwrap()) }) .collect::>>()?; @@ -685,7 +804,7 @@ impl CircuitData { .map(|b| { Ok(self .clbits - .find(other.clbits.get(*b).unwrap().bind(py)) + .py_find_bit(other.clbits.py_get_bit(py, *b)?.unwrap().bind(py)) .unwrap()) }) .collect::>>()?; @@ -824,16 +943,40 @@ impl CircuitData { } fn __traverse__(&self, visit: PyVisit<'_>) -> Result<(), PyTraverseError> { - for bit in self.qubits.bits().iter().chain(self.clbits.bits().iter()) { + for bit in self + .qubits + .py_bits_raw() + .iter() + .chain(self.clbits.py_bits_raw().iter()) + .filter_map(|cell| cell.get()) + { visit.call(bit)?; } - + for register in self + .qubits + .py_regs_raw() + .iter() + .chain(self.clbits.py_regs_raw().iter()) + .filter_map(|cell| cell.get()) + { + visit.call(register)?; + } // Note: // There's no need to visit the native Rust data // structures used for internal tracking: the only Python // references they contain are to the bits in these lists! - visit.call(self.qubits.cached())?; - visit.call(self.clbits.cached())?; + if let Some(bits) = self.qubits.py_bits_cached_raw() { + visit.call(bits)?; + } + if let Some(registers) = self.qubits.py_regs_cached_raw() { + visit.call(registers)?; + } + if let Some(bits) = self.clbits.py_bits_cached_raw() { + visit.call(bits)?; + } + if let Some(registers) = self.clbits.py_regs_cached_raw() { + visit.call(registers)?; + } self.param_table.py_gc_traverse(&visit)?; Ok(()) } @@ -897,6 +1040,89 @@ impl CircuitData { } impl CircuitData { + /// Rust native constructor for [CircuitData]. Builds a new instance without + /// any python initialization. + pub fn new( + num_qubits: u32, + num_clbits: u32, + global_phase: Param, + add_qreg: bool, + add_creg: bool, + ) -> Self { + let mut data = Self { + data: vec![], + qargs_interner: Interner::new(), + cargs_interner: Interner::new(), + qubits: NewBitData::with_capacity( + "qubits".to_owned(), + num_qubits + .try_into() + .expect("The number of qubits provided exceeds the limit for a circuit."), + 0, + ), + clbits: NewBitData::with_capacity( + "clbits".to_owned(), + num_clbits + .try_into() + .expect("The number of clbits provided exceeds the limit for a circuit."), + 0, + ), + param_table: ParameterTable::new(), + global_phase, + }; + // Add all the bits into a register + if add_qreg { + let indices: Vec = (0..data.qubits.len() as u32).map(Qubit).collect(); + data.add_qreg(Some("q".to_string()), None, Some(&indices)); + } + // Add all the bits into a register + if add_creg { + let indices: Vec = (0..data.clbits.len() as u32).map(Clbit).collect(); + data.add_creg(Some("c".to_string()), None, Some(&indices)); + } + data + } + + /// Adds a generic qubit to a circuit + pub fn add_qubit(&mut self) -> Qubit { + self.qubits.add_bit() + } + + /// Get qubit location in the circuit + pub fn get_qubit_location(&self, qubit: Qubit) -> &[BitLocation] { + self.qubits.get_bit_info(qubit) + } + + /// Adds either a generic register with new bits, or uses existing bit indices. + pub fn add_qreg( + &mut self, + name: Option, + num_qubits: Option, + bits: Option<&[Qubit]>, + ) -> u32 { + self.qubits.add_register(name, num_qubits, bits) + } + + /// Adds a generic qubit to a circuit + pub fn add_clbit(&mut self) -> Clbit { + self.clbits.add_bit() + } + + /// Adds either a generic register with new bits, or uses existing bit indices. + pub fn add_creg( + &mut self, + name: Option, + num_qubits: Option, + bits: Option<&[Clbit]>, + ) -> u32 { + self.clbits.add_register(name, num_qubits, bits) + } + + /// Get qubit location in the circuit + pub fn get_clbit_location(&self, clbit: Clbit) -> &[BitLocation] { + self.clbits.get_bit_info(clbit) + } + /// An alternate constructor to build a new `CircuitData` from an iterator /// of packed operations. This can be used to build a circuit from a sequence /// of `PackedOperation` without needing to involve Python. @@ -989,8 +1215,8 @@ impl CircuitData { /// * global_phase: The global phase value to use for the new circuit. pub fn from_packed_instructions( py: Python, - qubits: BitData, - clbits: BitData, + qubits: NewBitData, + clbits: NewBitData, qargs_interner: Interner<[Qubit]>, cargs_interner: Interner<[Clbit]>, instructions: I, @@ -1086,8 +1312,8 @@ impl CircuitData { data: Vec::with_capacity(instruction_capacity), qargs_interner: Interner::new(), cargs_interner: Interner::new(), - qubits: BitData::new(py, "qubits".to_string()), - clbits: BitData::new(py, "clbits".to_string()), + qubits: NewBitData::new("qubits".to_string()), + clbits: NewBitData::new("clbits".to_string()), param_table: ParameterTable::new(), global_phase: Param::Float(0.0), }; @@ -1100,14 +1326,14 @@ impl CircuitData { let qubit_cls = QUBIT.get_bound(py); for _i in 0..num_qubits { let bit = qubit_cls.call0()?; - res.add_qubit(py, &bit, true)?; + res.py_add_qubit(&bit, true)?; } } if num_clbits > 0 { let clbit_cls = CLBIT.get_bound(py); for _i in 0..num_clbits { let bit = clbit_cls.call0()?; - res.add_clbit(py, &bit, true)?; + res.py_add_clbit(&bit, true)?; } } Ok(res) @@ -1215,10 +1441,10 @@ impl CircuitData { fn pack(&mut self, py: Python, inst: &CircuitInstruction) -> PyResult { let qubits = self .qargs_interner - .insert_owned(self.qubits.map_bits(inst.qubits.bind(py))?.collect()); + .insert_owned(self.qubits.py_map_bits(inst.qubits.bind(py))?.collect()); let clbits = self .cargs_interner - .insert_owned(self.clbits.map_bits(inst.clbits.bind(py))?.collect()); + .insert_owned(self.clbits.py_map_bits(inst.clbits.bind(py))?.collect()); Ok(PackedInstruction { op: inst.operation.clone(), qubits, @@ -1295,12 +1521,12 @@ impl CircuitData { } /// Returns an immutable view of the Qubits registered in the circuit - pub fn qubits(&self) -> &BitData { + pub fn qubits(&self) -> &NewBitData { &self.qubits } /// Returns an immutable view of the Classical bits registered in the circuit - pub fn clbits(&self) -> &BitData { + pub fn clbits(&self) -> &NewBitData { &self.clbits } diff --git a/crates/circuit/src/converters.rs b/crates/circuit/src/converters.rs index b9b7b433eec8..32f45ba2e416 100644 --- a/crates/circuit/src/converters.rs +++ b/crates/circuit/src/converters.rs @@ -15,14 +15,11 @@ use std::sync::OnceLock; use hashbrown::HashMap; use pyo3::prelude::*; -use pyo3::{ - intern, - types::{PyDict, PyList}, -}; +use pyo3::{intern, types::PyDict}; -use crate::circuit_data::CircuitData; use crate::dag_circuit::{DAGCircuit, NodeType}; use crate::packed_instruction::PackedInstruction; +use crate::{bit_data::NewBitData, circuit_data::CircuitData}; /// An extractable representation of a QuantumCircuit reserved only for /// conversion purposes. @@ -32,8 +29,6 @@ pub struct QuantumCircuitData<'py> { pub name: Option>, pub calibrations: Option>>, pub metadata: Option>, - pub qregs: Option>, - pub cregs: Option>, pub input_vars: Vec>, pub captured_vars: Vec>, pub declared_vars: Vec>, @@ -52,14 +47,6 @@ impl<'py> FromPyObject<'py> for QuantumCircuitData<'py> { .extract() .ok(), metadata: ob.getattr(intern!(py, "metadata")).ok(), - qregs: ob - .getattr(intern!(py, "qregs")) - .map(|ob| ob.downcast_into())? - .ok(), - cregs: ob - .getattr(intern!(py, "cregs")) - .map(|ob| ob.downcast_into())? - .ok(), input_vars: ob .call_method0(intern!(py, "iter_input_vars"))? .try_iter()? @@ -99,10 +86,10 @@ pub fn dag_to_circuit( dag: &DAGCircuit, copy_operations: bool, ) -> PyResult { - CircuitData::from_packed_instructions( + let mut circuit = CircuitData::from_packed_instructions( py, - dag.qubits().clone(), - dag.clbits().clone(), + NewBitData::from_bit_data(py, dag.qubits()), + NewBitData::from_bit_data(py, dag.clbits()), dag.qargs_interner().clone(), dag.cargs_interner().clone(), dag.topological_op_nodes()?.map(|node_index| { @@ -133,7 +120,15 @@ pub fn dag_to_circuit( } }), dag.get_global_phase(), - ) + )?; + // Manually add qregs and cregs + for reg in dag.qregs.bind(py).values() { + circuit.py_add_qreg(®)?; + } + for reg in dag.cregs.bind(py).values() { + circuit.py_add_creg(®)?; + } + Ok(circuit) } pub fn converters(m: &Bound) -> PyResult<()> { diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 40133e979894..97bb639824ea 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -192,9 +192,9 @@ pub struct DAGCircuit { dag: StableDiGraph, #[pyo3(get)] - qregs: Py, + pub qregs: Py, #[pyo3(get)] - cregs: Py, + pub cregs: Py, /// The cache used to intern instruction qargs. pub qargs_interner: Interner<[Qubit]>, @@ -6586,7 +6586,7 @@ impl DAGCircuit { &qubit ))); } - let qubit_index = qc_data.qubits().find(&qubit).unwrap(); + let qubit_index = qc_data.qubits().py_find_bit(&qubit).unwrap(); ordered_vec[qubit_index.index()] = new_dag.add_qubit_unchecked(py, &qubit)?; Ok(()) })?; @@ -6594,7 +6594,7 @@ impl DAGCircuit { } else { qc_data .qubits() - .bits() + .py_bits(py)? .iter() .try_for_each(|qubit| -> PyResult<_> { new_dag.add_qubit_unchecked(py, qubit.bind(py))?; @@ -6615,7 +6615,7 @@ impl DAGCircuit { &clbit ))); }; - let clbit_index = qc_data.clbits().find(&clbit).unwrap(); + let clbit_index = qc_data.clbits().py_find_bit(&clbit).unwrap(); ordered_vec[clbit_index.index()] = new_dag.add_clbit_unchecked(py, &clbit)?; Ok(()) })?; @@ -6623,7 +6623,7 @@ impl DAGCircuit { } else { qc_data .clbits() - .bits() + .py_bits(py)? .iter() .try_for_each(|clbit| -> PyResult<()> { new_dag.add_clbit_unchecked(py, clbit.bind(py))?; @@ -6646,16 +6646,12 @@ impl DAGCircuit { } // Add all the registers - if let Some(qregs) = qc.qregs { - for qreg in qregs.iter() { - new_dag.add_qreg(py, &qreg)?; - } + for qreg in qc_data.py_qregs(py).bind(py).iter() { + new_dag.add_qreg(py, &qreg)?; } - if let Some(cregs) = qc.cregs { - for creg in cregs.iter() { - new_dag.add_creg(py, &creg)?; - } + for creg in qc_data.py_cregs(py).bind(py).iter() { + new_dag.add_creg(py, &creg)?; } // Pre-process and re-intern all indices again. @@ -6723,8 +6719,6 @@ impl DAGCircuit { name: None, calibrations: None, metadata: None, - qregs: None, - cregs: None, input_vars: Vec::new(), captured_vars: Vec::new(), declared_vars: Vec::new(), diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 585697796f61..a52620263c96 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/interner.rs b/crates/circuit/src/interner.rs index b77ecb51fa98..c4142035718f 100644 --- a/crates/circuit/src/interner.rs +++ b/crates/circuit/src/interner.rs @@ -196,6 +196,25 @@ where _type: PhantomData, } } + + /// Checks whether an item is already packed in the interner + pub fn contains(&self, value: &T) -> bool + where + T: Hash + Eq, + { + self.0.contains(value) + } + + /// Retrieves interner index of anitem if it exists + pub fn get_interned(&self, value: &T) -> Option> + where + T: Hash + Eq, + { + self.0.get_index_of(value).map(|index| Interned { + index: index as u32, + _type: PhantomData, + }) + } } #[cfg(test)] diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index ba120acecefb..cb1bf7f3a5c1 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -10,6 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +pub mod bit; pub mod bit_data; pub mod circuit_data; pub mod circuit_instruction; @@ -24,11 +25,13 @@ pub mod interner; pub mod operations; pub mod packed_instruction; pub mod parameter_table; +pub mod register; pub mod slice; pub mod util; mod rustworkx_core_vnext; +use imports::{CLBIT, QUBIT}; use pyo3::prelude::*; use pyo3::types::{PySequence, PyTuple}; @@ -122,6 +125,27 @@ 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()) + } +} + /// Implement `IntoPyObject` for the reference to a struct or enum declared as `#[pyclass]` that is /// also `Copy`. /// diff --git a/crates/circuit/src/register.rs b/crates/circuit/src/register.rs new file mode 100644 index 000000000000..dd9623fc1496 --- /dev/null +++ b/crates/circuit/src/register.rs @@ -0,0 +1,227 @@ +use indexmap::IndexSet; +use pyo3::{exceptions::PyTypeError, intern, types::PyAnyMethods, FromPyObject}; +use std::{ + hash::{Hash, Hasher}, + ops::Index, + sync::Mutex, +}; + +use crate::{ + imports::{CLASSICAL_REGISTER, QUANTUM_REGISTER, REGISTER}, + Clbit, Qubit, +}; + +/// 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)] +pub enum RegisterAsKey { + Register((String, u32)), + Quantum((String, u32)), + Classical((String, u32)), +} + +impl RegisterAsKey { + #[inline] + pub fn reduce(&self) -> (u32, &str) { + match self { + RegisterAsKey::Register(key) => (key.1, key.0.as_str()), + RegisterAsKey::Quantum(key) => (key.1, key.0.as_str()), + RegisterAsKey::Classical(key) => (key.1, key.0.as_str()), + } + } + + #[inline] + pub fn name(&self) -> &str { + match self { + RegisterAsKey::Register(key) => key.0.as_str(), + RegisterAsKey::Quantum(key) => key.0.as_str(), + RegisterAsKey::Classical(key) => key.0.as_str(), + } + } + + #[inline] + pub fn size(&self) -> u32 { + match self { + RegisterAsKey::Register(key) => key.1, + RegisterAsKey::Quantum(key) => key.1, + RegisterAsKey::Classical(key) => key.1, + } + } + + #[inline] + pub fn type_identifier(&self) -> &str { + match self { + RegisterAsKey::Register(_) => "Register", + RegisterAsKey::Quantum(_) => "QuantumRegister", + RegisterAsKey::Classical(_) => "ClassicalRegister", + } + } +} + +impl<'py> FromPyObject<'py> for RegisterAsKey { + fn extract_bound(ob: &pyo3::Bound<'py, pyo3::PyAny>) -> pyo3::PyResult { + 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.is_instance(CLASSICAL_REGISTER.get_bound(ob.py()))? { + return Ok(RegisterAsKey::Classical((name, num_qubits))); + } 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))); + } + } + Err(PyTypeError::new_err( + "The provided argument was not a register.", + )) + } +} +/// Described the desired behavior of a Register. +pub trait Register { + /// The type of bit stored by the [Register] + type Bit; + + /// 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, $key:expr) => { + static $counter: Mutex = Mutex::new(0); + + #[derive(Debug, Clone, Eq)] + pub struct $name { + register: IndexSet<<$name as Register>::Bit>, + key: RegisterAsKey, + } + + impl $name { + pub fn new(size: Option, name: Option, bits: Option<&[$bit]>) -> Self { + let register: IndexSet<<$name as Register>::Bit> = 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 { + let count = if let Ok(ref mut count) = $counter.try_lock() { + let curr = **count; + **count += 1; + curr + } else { + panic!("Could not access register counter.") + }; + format!("{}{}", $prefix, count) + }; + let length: u32 = register.len().try_into().unwrap(); + Self { + register, + key: $key((name, length)), + } + } + } + + impl Register for $name { + type Bit = $bit; + + fn len(&self) -> usize { + self.register.len() + } + + fn is_empty(&self) -> bool { + self.register.is_empty() + } + + fn name(&self) -> &str { + self.key.name() + } + + fn contains(&self, bit: Self::Bit) -> bool { + self.register.contains(&bit) + } + + fn find_index(&self, bit: Self::Bit) -> Option { + self.register.get_index_of(&bit).map(|idx| idx as u32) + } + + fn bits(&self) -> impl ExactSizeIterator { + self.register.iter().copied() + } + + fn as_key(&self) -> &RegisterAsKey { + &self.key + } + } + + impl Hash for $name { + fn hash(&self, state: &mut H) { + (self.key).hash(state); + } + } + + impl PartialEq for $name { + fn eq(&self, other: &Self) -> bool { + self.register.len() == other.register.len() && self.key == other.key + } + } + + impl Index for $name { + type Output = $bit; + + fn index(&self, index: usize) -> &Self::Output { + self.register.index(index) + } + } + + impl From<(usize, Option)> for $name { + fn from(value: (usize, Option)) -> Self { + Self::new(Some(value.0), value.1, None) + } + } + + impl From<&[$bit]> for $name { + fn from(value: &[$bit]) -> Self { + Self::new(None, None, Some(value)) + } + } + + impl From<(&[$bit], String)> for $name { + fn from(value: (&[$bit], String)) -> Self { + Self::new(None, Some(value.1), Some(value.0)) + } + } + }; +} + +create_register!( + QuantumRegister, + Qubit, + QREG_COUNTER, + "qr", + RegisterAsKey::Quantum +); + +create_register!( + ClassicalRegister, + Clbit, + CREG_COUNTER, + "cr", + RegisterAsKey::Classical +); diff --git a/qiskit/circuit/library/blueprintcircuit.py b/qiskit/circuit/library/blueprintcircuit.py index 90550a18948e..d49c738841e4 100644 --- a/qiskit/circuit/library/blueprintcircuit.py +++ b/qiskit/circuit/library/blueprintcircuit.py @@ -67,14 +67,23 @@ def _build(self) -> None: def _invalidate(self) -> None: """Invalidate the current circuit build.""" + # Take out the registers before invalidating + qregs = self._data.qregs + cregs = self._data.cregs self._data = CircuitData(self._data.qubits, self._data.clbits) + for qreg in qregs: + self._data.add_qreg(qreg) + for creg in cregs: + self._data.add_creg(creg) self.global_phase = 0 self._is_built = False @property def qregs(self): """A list of the quantum registers associated with the circuit.""" - return self._qregs + if not self._is_initialized: + return self._qregs + return super().qregs @qregs.setter def qregs(self, qregs): diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index ab0ad758efb9..92b4123d6f33 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1091,13 +1091,6 @@ def __init__( "qiskit.circuit.controlflow.builder.ControlFlowBuilderBlock" ] = [] - self.qregs: list[QuantumRegister] = [] - """A list of the :class:`QuantumRegister`\\ s in this circuit. You should not mutate - this.""" - self.cregs: list[ClassicalRegister] = [] - """A list of the :class:`ClassicalRegister`\\ s in this circuit. You should not mutate - this.""" - # Dict mapping Qubit or Clbit instances to tuple comprised of 0) the # corresponding index in circuit.{qubits,clbits} and 1) a list of # Register-int pairs for each Register containing the Bit and its index @@ -1175,7 +1168,8 @@ def _from_circuit_data( } else: out._qubit_indices = { - bit: BitLocations(index, []) for index, bit in enumerate(data.qubits) + bit: BitLocations(index, data.get_qubit_location(bit)) + for index, bit in enumerate(data.qubits) } if data.num_clbits > 0: @@ -1187,7 +1181,8 @@ def _from_circuit_data( } else: out._clbit_indices = { - bit: BitLocations(index, []) for index, bit in enumerate(data.clbits) + bit: BitLocations(index, data.get_clbit_location(bit)) + for index, bit in enumerate(data.clbits) } out._data = data @@ -1440,6 +1435,8 @@ def __deepcopy__(self, memo=None): result._data.replace_bits( qubits=_copy.deepcopy(self._data.qubits, memo), clbits=_copy.deepcopy(self._data.clbits, memo), + qregs=_copy.deepcopy(self._data.qregs, memo), + cregs=_copy.deepcopy(self._data.cregs, memo), ) return result @@ -2232,6 +2229,26 @@ def clbits(self) -> list[Clbit]: this.""" return self._data.clbits + @property + def qregs(self) -> list[QuantumRegister]: + """A list of :class:`Qubit`\\ s in the order that they were added. You should not mutate + this.""" + return self._data.qregs + + @qregs.setter + def qregs(self, other: list[QuantumRegister]): + self._data.qregs = other + + @property + def cregs(self) -> list[ClassicalRegister]: + """A list of :class:`Clbit`\\ s in the order that they were added. You should not mutate + this.""" + return self._data.cregs + + @cregs.setter + def cregs(self, other: list[ClassicalRegister]): + self._data.cregs = other + @property def ancillas(self) -> list[AncillaQubit]: """A list of :class:`AncillaQubit`\\ s in the order that they were added. You should not @@ -3070,13 +3087,12 @@ def add_register(self, *regs: Register | int | Sequence[Bit]) -> None: self._add_qreg(register) elif isinstance(register, ClassicalRegister): - self.cregs.append(register) + self._data.add_creg(register) for idx, bit in enumerate(register): if bit in self._clbit_indices: self._clbit_indices[bit].registers.append((register, idx)) else: - self._data.add_clbit(bit) self._clbit_indices[bit] = BitLocations( self._data.num_clbits - 1, [(register, idx)] ) @@ -3087,13 +3103,12 @@ def add_register(self, *regs: Register | int | Sequence[Bit]) -> None: raise CircuitError("expected a register") def _add_qreg(self, qreg: QuantumRegister) -> None: - self.qregs.append(qreg) + self._data.add_qreg(qreg) for idx, bit in enumerate(qreg): if bit in self._qubit_indices: self._qubit_indices[bit].registers.append((qreg, idx)) else: - self._data.add_qubit(bit) self._qubit_indices[bit] = BitLocations(self._data.num_qubits - 1, [(qreg, idx)]) def add_bits(self, bits: Iterable[Bit]) -> None: