diff --git a/Cargo.lock b/Cargo.lock index a9d8e40380f6..03f74d03aedb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -855,9 +855,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef41cbb417ea83b30525259e30ccef6af39b31c240bda578889494c5392d331" +checksum = "ec170733ca37175f5d75a5bea5911d6ff45d2cd52849ce98b685394e4f2f37f4" dependencies = [ "libc", "ndarray", @@ -1072,9 +1072,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.20.3" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53bdbb96d49157e65d45cc287af5f32ffadd5f4761438b527b055fb0d4bb8233" +checksum = "a7a8b1990bd018761768d5e608a13df8bd1ac5f678456e0f301bb93e5f3ea16b" dependencies = [ "cfg-if", "hashbrown 0.14.3", @@ -1095,9 +1095,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.20.3" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deaa5745de3f5231ce10517a1f5dd97d53e5a2fd77aa6b5842292085831d48d7" +checksum = "650dca34d463b6cdbdb02b1d71bfd6eb6b6816afc708faebb3bac1380ff4aef7" dependencies = [ "once_cell", "target-lexicon", @@ -1105,9 +1105,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.20.3" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b42531d03e08d4ef1f6e85a2ed422eb678b8cd62b762e53891c05faf0d4afa" +checksum = "09a7da8fc04a8a2084909b59f29e1b8474decac98b951d77b80b26dc45f046ad" dependencies = [ "libc", "pyo3-build-config", @@ -1115,9 +1115,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.20.3" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7305c720fa01b8055ec95e484a6eca7a83c841267f0dd5280f0c8b8551d2c158" +checksum = "4b8a199fce11ebb28e3569387228836ea98110e43a804a530a9fd83ade36d513" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1127,9 +1127,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.20.3" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c7e9b68bb9c3149c5b0cade5d07f953d6d125eb4337723c4ccdb665f1f96185" +checksum = "93fbbfd7eb553d10036513cb122b888dcd362a945a00b06c165f2ab480d4cc3b" dependencies = [ "heck", "proc-macro2", diff --git a/Cargo.toml b/Cargo.toml index dc3022507d87..33c62f42689f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ indexmap.version = "2.2.6" hashbrown.version = "0.14.0" # This doesn't set `extension-module` as a shared feature because we need to be able to disable it # during Rust-only testing (see # https://github.com/PyO3/pyo3/issues/340). -pyo3 = { version = "0.20.3", features = ["abi3-py38"] } +pyo3 = { version = "0.21.0", features = ["abi3-py38"] } [profile.release] lto = 'fat' diff --git a/crates/accelerate/Cargo.toml b/crates/accelerate/Cargo.toml index dcadcb2cf871..12e3c589192f 100644 --- a/crates/accelerate/Cargo.toml +++ b/crates/accelerate/Cargo.toml @@ -16,7 +16,7 @@ extension-module = ["pyo3/extension-module"] [dependencies] rayon = "1.10" -numpy = "0.20.0" +numpy = "0.21.0" rand = "0.8" rand_pcg = "0.3" rand_distr = "0.4.3" diff --git a/crates/accelerate/src/convert_2q_block_matrix.rs b/crates/accelerate/src/convert_2q_block_matrix.rs index 345054ecf1c3..e311c129b11b 100644 --- a/crates/accelerate/src/convert_2q_block_matrix.rs +++ b/crates/accelerate/src/convert_2q_block_matrix.rs @@ -57,7 +57,7 @@ pub fn blocks_to_matrix( None => op_matrix.dot(&matrix), }; } - Ok(matrix.into_pyarray(py).to_owned()) + Ok(matrix.into_pyarray_bound(py).unbind()) } /// Switches the order of qubits in a two qubit operation. @@ -75,7 +75,7 @@ pub fn change_basis(matrix: ArrayView2) -> Array2 { } #[pymodule] -pub fn convert_2q_block_matrix(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn convert_2q_block_matrix(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(blocks_to_matrix))?; Ok(()) } diff --git a/crates/accelerate/src/dense_layout.rs b/crates/accelerate/src/dense_layout.rs index 4570eafc0bf1..7cb54140761e 100644 --- a/crates/accelerate/src/dense_layout.rs +++ b/crates/accelerate/src/dense_layout.rs @@ -217,14 +217,14 @@ pub fn best_subset( let cols: Vec = new_cmap.iter().map(|edge| edge[1]).collect(); Ok(( - rows.to_pyarray(py).into(), - cols.to_pyarray(py).into(), - best_map.to_pyarray(py).into(), + rows.to_pyarray_bound(py).into(), + cols.to_pyarray_bound(py).into(), + best_map.to_pyarray_bound(py).into(), )) } #[pymodule] -pub fn dense_layout(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn dense_layout(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(best_subset))?; Ok(()) } diff --git a/crates/accelerate/src/edge_collections.rs b/crates/accelerate/src/edge_collections.rs index 50d75a4ba01f..b64cbc42c356 100644 --- a/crates/accelerate/src/edge_collections.rs +++ b/crates/accelerate/src/edge_collections.rs @@ -55,7 +55,7 @@ impl EdgeCollection { /// output array here would be ``[0, 1, 1, 2, 2, 3]``. #[pyo3(text_signature = "(self, /)")] pub fn edges(&self, py: Python) -> PyObject { - self.edges.clone().into_pyarray(py).into() + self.edges.clone().into_pyarray_bound(py).into() } fn __getstate__(&self) -> Vec { diff --git a/crates/accelerate/src/error_map.rs b/crates/accelerate/src/error_map.rs index 607813d3930b..8dbd2e4290a5 100644 --- a/crates/accelerate/src/error_map.rs +++ b/crates/accelerate/src/error_map.rs @@ -112,7 +112,7 @@ impl ErrorMap { } #[pymodule] -pub fn error_map(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn error_map(m: &Bound) -> PyResult<()> { m.add_class::()?; Ok(()) } diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 5f9f479f6006..de588c066301 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -18,6 +18,7 @@ use num_complex::{Complex64, ComplexFloat}; use smallvec::{smallvec, SmallVec}; use std::cmp::Ordering; use std::f64::consts::PI; +use std::ops::Deref; use pyo3::exceptions::{PyIndexError, PyValueError}; use pyo3::prelude::*; @@ -27,6 +28,7 @@ use pyo3::Python; use ndarray::prelude::*; use numpy::PyReadonlyArray2; +use pyo3::pybacked::PyBackedStr; use crate::utils::SliceOrInt; @@ -594,7 +596,11 @@ impl EulerBasis { #[pymethods] impl EulerBasis { fn __reduce__(&self, py: Python) -> Py { - (py.get_type::(), (PyString::new(py, self.as_str()),)).into_py(py) + ( + py.get_type_bound::(), + (PyString::new_bound(py, self.as_str()),), + ) + .into_py(py) } #[new] @@ -700,7 +706,7 @@ pub fn compute_error_list( #[pyo3(signature = (unitary, target_basis_list, qubit, error_map=None, simplify=true, atol=None))] pub fn unitary_to_gate_sequence( unitary: PyReadonlyArray2, - target_basis_list: Vec<&str>, + target_basis_list: Vec, qubit: usize, error_map: Option<&OneQubitGateErrorMap>, simplify: bool, @@ -708,7 +714,7 @@ pub fn unitary_to_gate_sequence( ) -> PyResult> { let mut target_basis_vec: Vec = Vec::with_capacity(target_basis_list.len()); for basis in target_basis_list { - let basis_enum = EulerBasis::from_str(basis)?; + let basis_enum = EulerBasis::from_str(basis.deref())?; target_basis_vec.push(basis_enum) } let unitary_mat = unitary.as_array(); @@ -873,7 +879,7 @@ pub fn params_zxz(unitary: PyReadonlyArray2) -> [f64; 4] { } #[pymodule] -pub fn euler_one_qubit_decomposer(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn euler_one_qubit_decomposer(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(params_zyz))?; m.add_wrapped(wrap_pyfunction!(params_xyx))?; m.add_wrapped(wrap_pyfunction!(params_xzx))?; diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index d34861a5cb1f..a9f0be1fd691 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -14,7 +14,6 @@ use std::env; use pyo3::prelude::*; use pyo3::wrap_pymodule; -use pyo3::Python; mod convert_2q_block_matrix; mod dense_layout; @@ -50,7 +49,7 @@ pub fn getenv_use_multiple_threads() -> bool { } #[pymodule] -fn _accelerate(_py: Python<'_>, m: &PyModule) -> PyResult<()> { +fn _accelerate(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(nlayout::nlayout))?; m.add_wrapped(wrap_pymodule!(stochastic_swap::stochastic_swap))?; m.add_wrapped(wrap_pymodule!(sabre_swap::sabre_swap))?; diff --git a/crates/accelerate/src/nlayout.rs b/crates/accelerate/src/nlayout.rs index 871d3696ddcc..53c0a468e822 100644 --- a/crates/accelerate/src/nlayout.rs +++ b/crates/accelerate/src/nlayout.rs @@ -57,8 +57,8 @@ macro_rules! qubit_newtype { unsafe impl numpy::Element for $id { const IS_COPY: bool = true; - fn get_dtype(py: Python<'_>) -> &numpy::PyArrayDescr { - u32::get_dtype(py) + fn get_dtype_bound(py: Python<'_>) -> Bound<'_, numpy::PyArrayDescr> { + u32::get_dtype_bound(py) } } }; @@ -139,7 +139,7 @@ impl NLayout { /// #[pyo3(text_signature = "(self, /)")] fn layout_mapping(&self, py: Python<'_>) -> Py { - PyList::new(py, self.iter_virtual()).into() + PyList::new_bound(py, self.iter_virtual()).into() } /// Get physical bit from virtual bit @@ -217,7 +217,7 @@ impl NLayout { } #[pymodule] -pub fn nlayout(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn nlayout(m: &Bound) -> PyResult<()> { m.add_class::()?; Ok(()) } diff --git a/crates/accelerate/src/optimize_1q_gates.rs b/crates/accelerate/src/optimize_1q_gates.rs index 450cf3f10a3b..a683604c19e0 100644 --- a/crates/accelerate/src/optimize_1q_gates.rs +++ b/crates/accelerate/src/optimize_1q_gates.rs @@ -12,7 +12,6 @@ use pyo3::prelude::*; use pyo3::wrap_pyfunction; -use pyo3::Python; const PI: f64 = std::f64::consts::PI; @@ -92,7 +91,7 @@ pub fn compose_u3_rust( } #[pymodule] -pub fn optimize_1q_gates(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn optimize_1q_gates(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(compose_u3_rust))?; Ok(()) } diff --git a/crates/accelerate/src/pauli_exp_val.rs b/crates/accelerate/src/pauli_exp_val.rs index 1c4974aef8ce..29f741f6cf46 100644 --- a/crates/accelerate/src/pauli_exp_val.rs +++ b/crates/accelerate/src/pauli_exp_val.rs @@ -193,7 +193,7 @@ pub fn density_expval_pauli_with_x( } #[pymodule] -pub fn pauli_expval(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn pauli_expval(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(expval_pauli_no_x))?; m.add_wrapped(wrap_pyfunction!(expval_pauli_with_x))?; m.add_wrapped(wrap_pyfunction!(density_expval_pauli_with_x))?; diff --git a/crates/accelerate/src/quantum_circuit/circuit_data.rs b/crates/accelerate/src/quantum_circuit/circuit_data.rs index 915285f6ad6d..0400ae0c3673 100644 --- a/crates/accelerate/src/quantum_circuit/circuit_data.rs +++ b/crates/accelerate/src/quantum_circuit/circuit_data.rs @@ -12,12 +12,11 @@ use crate::quantum_circuit::circuit_instruction::CircuitInstruction; use crate::quantum_circuit::intern_context::{BitType, IndexType, InternContext}; -use crate::quantum_circuit::py_ext; use crate::utils::SliceOrInt; use hashbrown::HashMap; use pyo3::exceptions::{PyIndexError, PyKeyError, PyRuntimeError, PyValueError}; use pyo3::prelude::*; -use pyo3::types::{PyIterator, PyList, PySet, PySlice, PyTuple, PyType}; +use pyo3::types::{PyList, PySet, PySlice, PyTuple, PyType}; use pyo3::{PyObject, PyResult, PyTraverseError, PyVisit}; use std::hash::{Hash, Hasher}; @@ -51,7 +50,7 @@ struct BitAsKey { } impl BitAsKey { - fn new(bit: &PyAny) -> PyResult { + fn new(bit: &Bound) -> PyResult { Ok(BitAsKey { hash: bit.hash()?, bit: bit.into_py(bit.py()), @@ -70,10 +69,10 @@ impl PartialEq for BitAsKey { self.bit.is(&other.bit) || Python::with_gil(|py| { self.bit - .as_ref(py) + .bind(py) .repr() .unwrap() - .eq(other.bit.as_ref(py).repr().unwrap()) + .eq(other.bit.bind(py).repr().unwrap()) .unwrap() }) } @@ -160,9 +159,9 @@ impl CircuitData { #[pyo3(signature = (qubits=None, clbits=None, data=None, reserve=0))] pub fn new( py: Python<'_>, - qubits: Option<&PyAny>, - clbits: Option<&PyAny>, - data: Option<&PyAny>, + qubits: Option<&Bound>, + clbits: Option<&Bound>, + data: Option<&Bound>, reserve: usize, ) -> PyResult { let mut self_ = CircuitData { @@ -172,17 +171,17 @@ impl CircuitData { clbits_native: Vec::new(), qubit_indices_native: HashMap::new(), clbit_indices_native: HashMap::new(), - qubits: PyList::empty(py).into_py(py), - clbits: PyList::empty(py).into_py(py), + qubits: PyList::empty_bound(py).unbind(), + clbits: PyList::empty_bound(py).unbind(), }; if let Some(qubits) = qubits { for bit in qubits.iter()? { - self_.add_qubit(py, bit?, true)?; + self_.add_qubit(py, &bit?, true)?; } } if let Some(clbits) = clbits { for bit in clbits.iter()? { - self_.add_clbit(py, bit?, true)?; + self_.add_clbit(py, &bit?, true)?; } } if let Some(data) = data { @@ -192,8 +191,8 @@ impl CircuitData { Ok(self_) } - pub fn __reduce__(self_: &PyCell, py: Python<'_>) -> PyResult { - let ty: &PyType = self_.get_type(); + pub fn __reduce__(self_: &Bound, py: Python<'_>) -> PyResult { + let ty: Bound = self_.get_type(); let args = { let self_ = self_.borrow(); ( @@ -245,8 +244,8 @@ impl CircuitData { /// 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: &PyAny, strict: bool) -> PyResult<()> { - if self.qubits_native.len() != self.qubits.as_ref(bit.py()).len() { + pub fn add_qubit(&mut self, py: Python, bit: &Bound, strict: bool) -> PyResult<()> { + if self.qubits_native.len() != self.qubits.bind(bit.py()).len() { return Err(PyRuntimeError::new_err(concat!( "This circuit's 'qubits' list has become out of sync with the circuit data.", " Did something modify it?" @@ -263,7 +262,7 @@ impl CircuitData { .is_ok() { self.qubits_native.push(bit.into_py(py)); - self.qubits.as_ref(py).append(bit)?; + self.qubits.bind(py).append(bit)?; } else if strict { return Err(PyValueError::new_err(format!( "Existing bit {:?} cannot be re-added in strict mode.", @@ -283,8 +282,8 @@ impl CircuitData { /// 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: &PyAny, strict: bool) -> PyResult<()> { - if self.clbits_native.len() != self.clbits.as_ref(bit.py()).len() { + pub fn add_clbit(&mut self, py: Python, bit: &Bound, strict: bool) -> PyResult<()> { + if self.clbits_native.len() != self.clbits.bind(bit.py()).len() { return Err(PyRuntimeError::new_err(concat!( "This circuit's 'clbits' list has become out of sync with the circuit data.", " Did something modify it?" @@ -301,7 +300,7 @@ impl CircuitData { .is_ok() { self.clbits_native.push(bit.into_py(py)); - self.clbits.as_ref(py).append(bit)?; + self.clbits.bind(py).append(bit)?; } else if strict { return Err(PyValueError::new_err(format!( "Existing bit {:?} cannot be re-added in strict mode.", @@ -318,8 +317,8 @@ impl CircuitData { pub fn copy(&self, py: Python<'_>) -> PyResult { let mut res = CircuitData::new( py, - Some(self.qubits.as_ref(py)), - Some(self.clbits.as_ref(py)), + Some(self.qubits.bind(py)), + Some(self.clbits.bind(py)), None, 0, )?; @@ -364,9 +363,9 @@ impl CircuitData { /// func (Callable[[:class:`~.Operation`], None]): /// The callable to invoke. #[pyo3(signature = (func))] - pub fn foreach_op(&self, py: Python<'_>, func: &PyAny) -> PyResult<()> { + pub fn foreach_op(&self, py: Python<'_>, func: &Bound) -> PyResult<()> { for inst in self.data.iter() { - func.call1((inst.op.as_ref(py),))?; + func.call1((inst.op.bind(py),))?; } Ok(()) } @@ -378,9 +377,9 @@ impl CircuitData { /// func (Callable[[int, :class:`~.Operation`], None]): /// The callable to invoke. #[pyo3(signature = (func))] - pub fn foreach_op_indexed(&self, py: Python<'_>, func: &PyAny) -> PyResult<()> { + pub fn foreach_op_indexed(&self, py: Python<'_>, func: &Bound) -> PyResult<()> { for (index, inst) in self.data.iter().enumerate() { - func.call1((index, inst.op.as_ref(py)))?; + func.call1((index, inst.op.bind(py)))?; } Ok(()) } @@ -393,9 +392,9 @@ impl CircuitData { /// A callable used to map original operation to their /// replacements. #[pyo3(signature = (func))] - pub fn map_ops(&mut self, py: Python<'_>, func: &PyAny) -> PyResult<()> { + pub fn map_ops(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> { for inst in self.data.iter_mut() { - inst.op = func.call1((inst.op.as_ref(py),))?.into_py(py); + inst.op = func.call1((inst.op.bind(py),))?.into_py(py); } Ok(()) } @@ -455,8 +454,8 @@ impl CircuitData { pub fn replace_bits( &mut self, py: Python<'_>, - qubits: Option<&PyAny>, - clbits: Option<&PyAny>, + qubits: Option<&Bound>, + clbits: Option<&Bound>, ) -> PyResult<()> { let mut temp = CircuitData::new(py, qubits, clbits, None, 0)?; if temp.qubits_native.len() < self.qubits_native.len() { @@ -497,7 +496,7 @@ impl CircuitData { } // Note: we also rely on this to make us iterable! - pub fn __getitem__(&self, py: Python, index: &PyAny) -> PyResult { + pub fn __getitem__(&self, py: Python, index: &Bound) -> PyResult { // Internal helper function to get a specific // instruction by index. fn get_at( @@ -532,7 +531,7 @@ impl CircuitData { match index { SliceOrInt::Slice(slice) => { let slice = { - let mut s = self.convert_py_slice(slice)?; + let mut s = self.convert_py_slice(&slice)?; if s.len() > 1 && s.first().unwrap() < s.last().unwrap() { // Reverse the order so we're sure to delete items // at the back first (avoids messing up indices). @@ -564,13 +563,13 @@ impl CircuitData { &mut self, py: Python<'_>, index: SliceOrInt, - value: &PyAny, + value: &Bound, ) -> PyResult<()> { match index { SliceOrInt::Slice(slice) => { let indices = slice.indices(self.data.len().try_into().unwrap())?; - let slice = self.convert_py_slice(slice)?; - let values = value.iter()?.collect::>>()?; + let slice = self.convert_py_slice(&slice)?; + let values = value.iter()?.collect::>>>()?; if indices.step != 1 && slice.len() != values.len() { // A replacement of a different length when step isn't exactly '1' // would result in holes. @@ -587,7 +586,7 @@ impl CircuitData { if slice.len() > values.len() { // Delete any extras. - let slice = PySlice::new( + let slice = PySlice::new_bound( py, indices.start + values.len() as isize, indices.stop, @@ -629,8 +628,8 @@ impl CircuitData { pub fn pop(&mut self, py: Python<'_>, index: Option) -> PyResult { let index = index.unwrap_or_else(|| std::cmp::max(0, self.data.len() as isize - 1).into_py(py)); - let item = self.__getitem__(py, index.as_ref(py))?; - self.__delitem__(index.as_ref(py).extract()?)?; + let item = self.__getitem__(py, index.bind(py))?; + self.__delitem__(index.bind(py).extract()?)?; Ok(item) } @@ -640,11 +639,7 @@ impl CircuitData { Ok(()) } - // To prevent the entire iterator from being loaded into memory, - // this function creates a `GILPool` for each iteration of the loop, which - // ensures that the `CircuitInstruction` returned by the call - // to `next` is dropped before the next iteration. - pub fn extend(&mut self, py: Python<'_>, itr: &PyAny) -> PyResult<()> { + pub fn extend(&mut self, py: Python<'_>, itr: &Bound) -> PyResult<()> { if let Ok(other) = itr.extract::>() { // Fast path to avoid unnecessary construction of // CircuitInstruction instances. @@ -656,7 +651,7 @@ impl CircuitData { .iter() .map(|b| { Ok(self.qubit_indices_native - [&BitAsKey::new(other.qubits_native[*b as usize].as_ref(py))?]) + [&BitAsKey::new(other.qubits_native[*b as usize].bind(py))?]) }) .collect::>>()?; let clbits = other @@ -665,7 +660,7 @@ impl CircuitData { .iter() .map(|b| { Ok(self.clbit_indices_native - [&BitAsKey::new(other.clbits_native[*b as usize].as_ref(py))?]) + [&BitAsKey::new(other.clbits_native[*b as usize].bind(py))?]) }) .collect::>>()?; @@ -678,33 +673,8 @@ impl CircuitData { return Ok(()); } - // To ensure proper lifetime management, we explicitly store - // the result of calling `iter(itr)` as a GIL-independent - // reference that we access only with the most recent GILPool. - // It would be dangerous to access the original `itr` or any - // GIL-dependent derivatives of it after creating the new pool. - let itr: Py = itr.iter()?.into_py(py); - loop { - // Create a new pool, so that PyO3 can clear memory at - // the end of the loop. - let pool = unsafe { py.new_pool() }; - - // It is recommended to *always* immediately set py to the pool's - // Python, to help avoid creating references with invalid lifetimes. - let py = pool.python(); - - // Access the iterator using the new pool. - match itr.as_ref(py).next() { - None => { - break; - } - Some(v) => { - self.append(py, v?.extract()?)?; - } - } - // The GILPool is dropped here, which cleans up the ref - // returned from `next` as well as any resources used by - // `self.append`. + for v in itr.iter()? { + self.append(py, v?.extract()?)?; } Ok(()) } @@ -718,8 +688,8 @@ impl CircuitData { #[classattr] const __hash__: Option> = None; - fn __eq__(slf: &PyCell, other: &PyAny) -> PyResult { - let slf: &PyAny = slf; + fn __eq__(slf: &Bound, other: &Bound) -> PyResult { + let slf = slf.as_any(); if slf.is(other) { return Ok(true); } @@ -777,7 +747,7 @@ impl CircuitData { impl CircuitData { /// Converts a Python slice to a `Vec` of indices into /// the instruction listing, [CircuitData.data]. - fn convert_py_slice(&self, slice: &PySlice) -> PyResult> { + fn convert_py_slice(&self, slice: &Bound) -> PyResult> { let indices = slice.indices(self.data.len().try_into().unwrap())?; if indices.step > 0 { Ok((indices.start..indices.stop) @@ -833,11 +803,11 @@ impl CircuitData { inst: PyRef, ) -> PyResult { let mut interned_bits = - |indices: &HashMap, bits: &PyTuple| -> PyResult { + |indices: &HashMap, bits: &Bound| -> PyResult { let args = bits .into_iter() .map(|b| { - let key = BitAsKey::new(b)?; + let key = BitAsKey::new(&b)?; indices.get(&key).copied().ok_or_else(|| { PyKeyError::new_err(format!( "Bit {:?} has not been added to this circuit.", @@ -850,8 +820,8 @@ impl CircuitData { }; Ok(PackedInstruction { op: inst.operation.clone_ref(py), - qubits_id: interned_bits(&self.qubit_indices_native, inst.qubits.as_ref(py))?, - clbits_id: interned_bits(&self.clbit_indices_native, inst.clbits.as_ref(py))?, + qubits_id: interned_bits(&self.qubit_indices_native, inst.qubits.bind(py))?, + clbits_id: interned_bits(&self.clbit_indices_native, inst.clbits.bind(py))?, }) } @@ -860,22 +830,24 @@ impl CircuitData { py, CircuitInstruction { operation: inst.op.clone_ref(py), - qubits: py_ext::tuple_new( + qubits: PyTuple::new_bound( py, self.intern_context .lookup(inst.qubits_id) .iter() .map(|i| self.qubits_native[*i as usize].clone_ref(py)) - .collect(), - ), - clbits: py_ext::tuple_new( + .collect::>(), + ) + .unbind(), + clbits: PyTuple::new_bound( py, self.intern_context .lookup(inst.clbits_id) .iter() .map(|i| self.clbits_native[*i as usize].clone_ref(py)) - .collect(), - ), + .collect::>(), + ) + .unbind(), }, ) } diff --git a/crates/accelerate/src/quantum_circuit/circuit_instruction.rs b/crates/accelerate/src/quantum_circuit/circuit_instruction.rs index 0bf84a362c3f..8dc9a5d1d21a 100644 --- a/crates/accelerate/src/quantum_circuit/circuit_instruction.rs +++ b/crates/accelerate/src/quantum_circuit/circuit_instruction.rs @@ -10,7 +10,6 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use crate::quantum_circuit::py_ext; use pyo3::basic::CompareOp; use pyo3::prelude::*; use pyo3::types::{PyList, PyTuple}; @@ -70,26 +69,27 @@ impl CircuitInstruction { pub fn new( py: Python<'_>, operation: PyObject, - qubits: Option<&PyAny>, - clbits: Option<&PyAny>, + qubits: Option<&Bound>, + clbits: Option<&Bound>, ) -> PyResult { - fn as_tuple(py: Python<'_>, seq: Option<&PyAny>) -> PyResult> { + fn as_tuple(py: Python<'_>, seq: Option<&Bound>) -> PyResult> { match seq { - None => Ok(py_ext::tuple_new_empty(py)), + None => Ok(PyTuple::empty_bound(py).unbind()), Some(seq) => { if seq.is_instance_of::() { Ok(seq.downcast_exact::()?.into_py(py)) } else if seq.is_instance_of::() { let seq = seq.downcast_exact::()?; - Ok(py_ext::tuple_from_list(seq)) + Ok(seq.to_tuple().unbind()) } else { // New tuple from iterable. - Ok(py_ext::tuple_new( + Ok(PyTuple::new_bound( py, seq.iter()? - .map(|o| Ok(o?.into_py(py))) + .map(|o| Ok(o?.unbind())) .collect::>>()?, - )) + ) + .unbind()) } } } @@ -118,27 +118,27 @@ impl CircuitInstruction { &self, py: Python<'_>, operation: Option, - qubits: Option<&PyAny>, - clbits: Option<&PyAny>, + qubits: Option<&Bound>, + clbits: Option<&Bound>, ) -> PyResult { CircuitInstruction::new( py, operation.unwrap_or_else(|| self.operation.clone_ref(py)), - Some(qubits.unwrap_or_else(|| self.qubits.as_ref(py))), - Some(clbits.unwrap_or_else(|| self.clbits.as_ref(py))), + Some(qubits.unwrap_or_else(|| self.qubits.bind(py))), + Some(clbits.unwrap_or_else(|| self.clbits.bind(py))), ) } fn __getstate__(&self, py: Python<'_>) -> PyObject { ( - self.operation.as_ref(py), - self.qubits.as_ref(py), - self.clbits.as_ref(py), + self.operation.bind(py), + self.qubits.bind(py), + self.clbits.bind(py), ) .into_py(py) } - fn __setstate__(&mut self, _py: Python<'_>, state: &PyTuple) -> PyResult<()> { + fn __setstate__(&mut self, _py: Python<'_>, state: &Bound) -> PyResult<()> { self.operation = state.get_item(0)?.extract()?; self.qubits = state.get_item(1)?.extract()?; self.clbits = state.get_item(2)?.extract()?; @@ -147,15 +147,15 @@ impl CircuitInstruction { pub fn __getnewargs__(&self, py: Python<'_>) -> PyResult { Ok(( - self.operation.as_ref(py), - self.qubits.as_ref(py), - self.clbits.as_ref(py), + self.operation.bind(py), + self.qubits.bind(py), + self.clbits.bind(py), ) .into_py(py)) } - pub fn __repr__(self_: &PyCell, py: Python<'_>) -> PyResult { - let type_name = self_.get_type().name()?; + pub fn __repr__(self_: &Bound, py: Python<'_>) -> PyResult { + let type_name = self_.get_type().qualname()?; let r = self_.try_borrow()?; Ok(format!( "{}(\ @@ -164,9 +164,9 @@ impl CircuitInstruction { , clbits={}\ )", type_name, - r.operation.as_ref(py).repr()?, - r.qubits.as_ref(py).repr()?, - r.clbits.as_ref(py).repr()? + r.operation.bind(py).repr()?, + r.qubits.bind(py).repr()?, + r.clbits.bind(py).repr()? )) } @@ -176,28 +176,23 @@ impl CircuitInstruction { // the interface to behave exactly like the old 3-tuple `(inst, qargs, cargs)` if it's treated // like that via unpacking or similar. That means that the `parameters` field is completely // absent, and the qubits and clbits must be converted to lists. - pub fn _legacy_format(&self, py: Python<'_>) -> PyObject { - PyTuple::new( + pub fn _legacy_format<'py>(&self, py: Python<'py>) -> Bound<'py, PyTuple> { + PyTuple::new_bound( py, [ - self.operation.as_ref(py), - self.qubits.as_ref(py).to_list(), - self.clbits.as_ref(py).to_list(), + self.operation.bind(py), + &self.qubits.bind(py).to_list(), + &self.clbits.bind(py).to_list(), ], ) - .into_py(py) } - pub fn __getitem__(&self, py: Python<'_>, key: &PyAny) -> PyResult { - Ok(self - ._legacy_format(py) - .as_ref(py) - .get_item(key)? - .into_py(py)) + pub fn __getitem__(&self, py: Python<'_>, key: &Bound) -> PyResult { + Ok(self._legacy_format(py).as_any().get_item(key)?.into_py(py)) } pub fn __iter__(&self, py: Python<'_>) -> PyResult { - Ok(self._legacy_format(py).as_ref(py).iter()?.into_py(py)) + Ok(self._legacy_format(py).as_any().iter()?.into_py(py)) } pub fn __len__(&self) -> usize { @@ -205,15 +200,15 @@ impl CircuitInstruction { } pub fn __richcmp__( - self_: &PyCell, - other: &PyAny, + self_: &Bound, + other: &Bound, op: CompareOp, py: Python<'_>, ) -> PyResult { fn eq( py: Python<'_>, - self_: &PyCell, - other: &PyAny, + self_: &Bound, + other: &Bound, ) -> PyResult> { if self_.is(other) { return Ok(Some(true)); @@ -221,19 +216,19 @@ impl CircuitInstruction { let self_ = self_.try_borrow()?; if other.is_instance_of::() { - let other: PyResult<&PyCell> = other.extract(); + let other: PyResult> = other.extract(); return other.map_or(Ok(Some(false)), |v| { let v = v.try_borrow()?; Ok(Some( - self_.clbits.as_ref(py).eq(v.clbits.as_ref(py))? - && self_.qubits.as_ref(py).eq(v.qubits.as_ref(py))? - && self_.operation.as_ref(py).eq(v.operation.as_ref(py))?, + self_.clbits.bind(py).eq(v.clbits.bind(py))? + && self_.qubits.bind(py).eq(v.qubits.bind(py))? + && self_.operation.bind(py).eq(v.operation.bind(py))?, )) }); } if other.is_instance_of::() { - return Ok(Some(self_._legacy_format(py).as_ref(py).eq(other)?)); + return Ok(Some(self_._legacy_format(py).eq(other)?)); } Ok(None) diff --git a/crates/accelerate/src/quantum_circuit/mod.rs b/crates/accelerate/src/quantum_circuit/mod.rs index 4f4b56865034..2b94619c6f39 100644 --- a/crates/accelerate/src/quantum_circuit/mod.rs +++ b/crates/accelerate/src/quantum_circuit/mod.rs @@ -13,12 +13,11 @@ pub mod circuit_data; pub mod circuit_instruction; pub mod intern_context; -mod py_ext; use pyo3::prelude::*; #[pymodule] -pub fn quantum_circuit(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn quantum_circuit(m: &Bound) -> PyResult<()> { m.add_class::()?; m.add_class::()?; Ok(()) diff --git a/crates/accelerate/src/quantum_circuit/py_ext.rs b/crates/accelerate/src/quantum_circuit/py_ext.rs deleted file mode 100644 index da27764a7f4e..000000000000 --- a/crates/accelerate/src/quantum_circuit/py_ext.rs +++ /dev/null @@ -1,45 +0,0 @@ -// This code is part of Qiskit. -// -// (C) Copyright IBM 2023 -// -// This code is licensed under the Apache License, Version 2.0. You may -// obtain a copy of this license in the LICENSE.txt file in the root directory -// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -// -// Any modifications or derivative works of this code must retain this -// copyright notice, and modified files need to carry a notice indicating -// that they have been altered from the originals. -//! Contains helper functions for creating [Py] (GIL-independent) -//! objects without creating an intermediate owned reference. These functions -//! are faster than PyO3's list and tuple factory methods when the caller -//! doesn't need to dereference the newly constructed object (i.e. if the -//! resulting [Py] will simply be stored in a Rust struct). -//! -//! The reason this is faster is because PyO3 tracks owned references and -//! will perform deallocation when the active [GILPool] goes out of scope. -//! If we don't need to dereference the [Py], then we can skip the -//! tracking and deallocation. - -use pyo3::ffi::Py_ssize_t; -use pyo3::prelude::*; -use pyo3::types::{PyList, PyTuple}; -use pyo3::{ffi, AsPyPointer, PyNativeType}; - -pub fn tuple_new(py: Python<'_>, items: Vec) -> Py { - unsafe { - let ptr = ffi::PyTuple_New(items.len() as Py_ssize_t); - let tup: Py = Py::from_owned_ptr(py, ptr); - for (i, obj) in items.into_iter().enumerate() { - ffi::PyTuple_SetItem(ptr, i as Py_ssize_t, obj.into_ptr()); - } - tup - } -} - -pub fn tuple_new_empty(py: Python<'_>) -> Py { - unsafe { Py::from_owned_ptr(py, ffi::PyTuple_New(0)) } -} - -pub fn tuple_from_list(list: &PyList) -> Py { - unsafe { Py::from_owned_ptr(list.py(), ffi::PyList_AsTuple(list.as_ptr())) } -} diff --git a/crates/accelerate/src/results/marginalization.rs b/crates/accelerate/src/results/marginalization.rs index 7a91305b6158..5260287e771d 100644 --- a/crates/accelerate/src/results/marginalization.rs +++ b/crates/accelerate/src/results/marginalization.rs @@ -181,7 +181,7 @@ pub fn marginal_measure_level_0( let new_shape = [input_shape[0], indices.len(), input_shape[2]]; let out_arr: Array3 = Array3::from_shape_fn(new_shape, |(i, j, k)| mem_arr[[i, indices[j], k]]); - out_arr.into_pyarray(py).into() + out_arr.into_pyarray_bound(py).into() } #[pyfunction] @@ -195,7 +195,7 @@ pub fn marginal_measure_level_0_avg( let new_shape = [indices.len(), input_shape[1]]; let out_arr: Array2 = Array2::from_shape_fn(new_shape, |(i, j)| mem_arr[[indices[i], j]]); - out_arr.into_pyarray(py).into() + out_arr.into_pyarray_bound(py).into() } #[pyfunction] @@ -209,7 +209,7 @@ pub fn marginal_measure_level_1( let new_shape = [input_shape[0], indices.len()]; let out_arr: Array2 = Array2::from_shape_fn(new_shape, |(i, j)| mem_arr[[i, indices[j]]]); - out_arr.into_pyarray(py).into() + out_arr.into_pyarray_bound(py).into() } #[pyfunction] @@ -220,5 +220,5 @@ pub fn marginal_measure_level_1_avg( ) -> PyResult { let mem_arr: &[Complex64] = memory.as_slice()?; let out_arr: Vec = indices.into_iter().map(|idx| mem_arr[idx]).collect(); - Ok(out_arr.into_pyarray(py).into()) + Ok(out_arr.into_pyarray_bound(py).into()) } diff --git a/crates/accelerate/src/results/mod.rs b/crates/accelerate/src/results/mod.rs index 2ac40bb307fe..36282a749e66 100644 --- a/crates/accelerate/src/results/mod.rs +++ b/crates/accelerate/src/results/mod.rs @@ -17,7 +17,7 @@ use pyo3::prelude::*; use pyo3::wrap_pyfunction; #[pymodule] -pub fn results(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn results(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(marginalization::marginal_counts))?; m.add_wrapped(wrap_pyfunction!(marginalization::marginal_distribution))?; m.add_wrapped(wrap_pyfunction!(marginalization::marginal_memory))?; diff --git a/crates/accelerate/src/sabre_layout.rs b/crates/accelerate/src/sabre_layout.rs index decfe590c050..343beb05ebae 100644 --- a/crates/accelerate/src/sabre_layout.rs +++ b/crates/accelerate/src/sabre_layout.rs @@ -105,10 +105,10 @@ pub fn sabre_layout_and_routing( }; ( res.0, - PyArray::from_vec(py, res.1).into(), + PyArray::from_vec_bound(py, res.1).into(), ( res.2.map, - res.2.node_order.into_pyarray(py).into(), + res.2.node_order.into_pyarray_bound(py).into(), res.2.node_block_results, ), ) @@ -216,7 +216,7 @@ fn layout_trial( } #[pymodule] -pub fn sabre_layout(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn sabre_layout(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(sabre_layout_and_routing))?; Ok(()) } diff --git a/crates/accelerate/src/sabre_swap/mod.rs b/crates/accelerate/src/sabre_swap/mod.rs index 29337d5d1e84..c36a81233d27 100644 --- a/crates/accelerate/src/sabre_swap/mod.rs +++ b/crates/accelerate/src/sabre_swap/mod.rs @@ -75,7 +75,7 @@ pub struct SabreResult { impl SabreResult { #[getter] fn node_order(&self, py: Python) -> PyObject { - self.node_order.to_pyarray(py).into() + self.node_order.to_pyarray_bound(py).into() } } @@ -102,7 +102,7 @@ impl NodeBlockResults { .iter() .map(|x| x.clone().into_py(py)) .collect::>() - .into_pyarray(py) + .into_pyarray_bound(py) .into()), None => Err(PyIndexError::new_err(format!( "Node index {object} has no block results", @@ -131,7 +131,7 @@ impl BlockResult { .iter() .map(|x| x.into_py(py)) .collect::>() - .into_pyarray(py) + .into_pyarray_bound(py) .into() } } @@ -243,9 +243,9 @@ pub fn build_swap_map( ); ( res.map, - res.node_order.into_pyarray(py).into(), + res.node_order.into_pyarray_bound(py).into(), res.node_block_results, - PyArray::from_iter( + PyArray::from_iter_bound( py, (0..num_qubits).map(|phys| { PhysicalQubit::new(phys) @@ -760,7 +760,7 @@ fn choose_best_swap( } #[pymodule] -pub fn sabre_swap(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn sabre_swap(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(build_swap_map))?; m.add_class::()?; m.add_class::()?; diff --git a/crates/accelerate/src/sabre_swap/neighbor_table.rs b/crates/accelerate/src/sabre_swap/neighbor_table.rs index a795436caceb..be92153e7e8a 100644 --- a/crates/accelerate/src/sabre_swap/neighbor_table.rs +++ b/crates/accelerate/src/sabre_swap/neighbor_table.rs @@ -104,22 +104,22 @@ impl NeighborTable { } fn __getstate__(&self, py: Python<'_>) -> Py { - PyList::new( + PyList::new_bound( py, self.neighbors .iter() - .map(|v| PyList::new(py, v.iter()).to_object(py)), + .map(|v| PyList::new_bound(py, v.iter()).to_object(py)), ) .into() } - fn __setstate__(&mut self, state: &PyList) -> PyResult<()> { + fn __setstate__(&mut self, state: &Bound) -> PyResult<()> { self.neighbors = state .iter() .map(|v| { v.downcast::()? .iter() - .map(PyAny::extract) + .map(|b| b.extract()) .collect::>() }) .collect::>()?; diff --git a/crates/accelerate/src/sampled_exp_val.rs b/crates/accelerate/src/sampled_exp_val.rs index d71219ff16f6..b51ca3c98f0e 100644 --- a/crates/accelerate/src/sampled_exp_val.rs +++ b/crates/accelerate/src/sampled_exp_val.rs @@ -87,7 +87,7 @@ pub fn sampled_expval_complex( } #[pymodule] -pub fn sampled_exp_val(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn sampled_exp_val(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(sampled_expval_float))?; m.add_wrapped(wrap_pyfunction!(sampled_expval_complex))?; Ok(()) diff --git a/crates/accelerate/src/sparse_pauli_op.rs b/crates/accelerate/src/sparse_pauli_op.rs index aa97dd64b180..c8e0f0fb9316 100644 --- a/crates/accelerate/src/sparse_pauli_op.rs +++ b/crates/accelerate/src/sparse_pauli_op.rs @@ -19,7 +19,7 @@ use hashbrown::HashMap; use ndarray::{s, Array1, Array2, ArrayView1, ArrayView2, Axis}; use num_complex::Complex64; use num_traits::Zero; -use numpy::{IntoPyArray, PyArray1, PyArray2, PyReadonlyArray2}; +use numpy::{IntoPyArray, PyArray1, PyArray2, PyReadonlyArray2, PyUntypedArrayMethods}; /// Find the unique elements of an array. /// @@ -58,8 +58,8 @@ pub fn unordered_unique(py: Python, array: PyReadonlyArray2) -> (PyObject, } } ( - indices.into_pyarray(py).into(), - inverses.into_pyarray(py).into(), + indices.into_pyarray_bound(py).into(), + inverses.into_pyarray_bound(py).into(), ) } @@ -120,10 +120,10 @@ pub fn decompose_dense( } if coeffs.is_empty() { Ok(ZXPaulis { - z: PyArray2::zeros(py, [0, num_qubits], false).into(), - x: PyArray2::zeros(py, [0, num_qubits], false).into(), - phases: PyArray1::zeros(py, [0], false).into(), - coeffs: PyArray1::zeros(py, [0], false).into(), + z: PyArray2::zeros_bound(py, [0, num_qubits], false).into(), + x: PyArray2::zeros_bound(py, [0, num_qubits], false).into(), + phases: PyArray1::zeros_bound(py, [0], false).into(), + coeffs: PyArray1::zeros_bound(py, [0], false).into(), }) } else { // Constructing several arrays of different shapes at once is rather awkward in iterator @@ -163,10 +163,10 @@ pub fn decompose_dense( let x = unsafe { x.assume_init() }; let phases = unsafe { phases.assume_init() }; Ok(ZXPaulis { - z: z.into_pyarray(py).into(), - x: x.into_pyarray(py).into(), - phases: phases.into_pyarray(py).into(), - coeffs: PyArray1::from_vec(py, coeffs).into(), + z: z.into_pyarray_bound(py).into(), + x: x.into_pyarray_bound(py).into(), + phases: phases.into_pyarray_bound(py).into(), + coeffs: PyArray1::from_vec_bound(py, coeffs).into(), }) } } @@ -258,7 +258,7 @@ fn decompose_dense_inner( } #[pymodule] -pub fn sparse_pauli_op(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn sparse_pauli_op(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(unordered_unique))?; m.add_wrapped(wrap_pyfunction!(decompose_dense))?; m.add_class::()?; diff --git a/crates/accelerate/src/stochastic_swap.rs b/crates/accelerate/src/stochastic_swap.rs index 6d5ed693be42..bc13325d8d96 100644 --- a/crates/accelerate/src/stochastic_swap.rs +++ b/crates/accelerate/src/stochastic_swap.rs @@ -25,7 +25,6 @@ use rayon::prelude::*; use pyo3::prelude::*; use pyo3::wrap_pyfunction; -use pyo3::Python; use rand::prelude::*; use rand_distr::{Distribution, Normal}; @@ -255,7 +254,7 @@ pub fn swap_trials( let cdist_arr = cdist.as_array(); let cdist2_arr = cdist2.as_array(); let edges_arr = edges.as_slice()?; - let num_gates: usize = int_gates.len() / 2; + let num_gates: usize = int_gates.len()? / 2; let mut best_possible: Option<(u64, f64, EdgeCollection, NLayout)> = None; let locked_best_possible: RwLock<&mut Option<(u64, f64, EdgeCollection, NLayout)>> = RwLock::new(&mut best_possible); @@ -337,7 +336,7 @@ pub fn swap_trials( } #[pymodule] -pub fn stochastic_swap(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn stochastic_swap(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(swap_trials))?; m.add_class::()?; Ok(()) diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index d1dc67c2d0d6..cef99a5c4788 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -28,6 +28,7 @@ use pyo3::wrap_pyfunction; use pyo3::Python; use smallvec::{smallvec, SmallVec}; use std::f64::consts::{FRAC_1_SQRT_2, PI}; +use std::ops::Deref; use faer::IntoFaerComplex; use faer::IntoNdarray; @@ -40,6 +41,7 @@ use ndarray::prelude::*; use ndarray::Zip; use numpy::PyReadonlyArray2; use numpy::{IntoPyArray, ToPyArray}; +use pyo3::pybacked::PyBackedStr; use crate::convert_2q_block_matrix::change_basis; use crate::euler_one_qubit_decomposer::{ @@ -467,7 +469,7 @@ impl Specialization { Self::fSimabbEquiv => 8, Self::fSimabmbEquiv => 9, }; - Ok((py.get_type::().getattr("_from_u8")?, (val,)).into_py(py)) + Ok((py.get_type_bound::().getattr("_from_u8")?, (val,)).into_py(py)) } #[staticmethod] @@ -597,15 +599,15 @@ impl TwoQubitWeylDecomposition { fn __reduce__(&self, py: Python) -> PyResult> { Ok(( - py.get_type::().getattr("_from_state")?, + py.get_type_bound::().getattr("_from_state")?, ( [self.a, self.b, self.c, self.global_phase], [ - self.K1l.to_pyarray(py), - self.K1r.to_pyarray(py), - self.K2l.to_pyarray(py), - self.K2r.to_pyarray(py), - self.unitary_matrix.to_pyarray(py), + self.K1l.to_pyarray_bound(py), + self.K1r.to_pyarray_bound(py), + self.K2l.to_pyarray_bound(py), + self.K2r.to_pyarray_bound(py), + self.unitary_matrix.to_pyarray_bound(py), ], self.specialization, self.default_euler_basis, @@ -1100,41 +1102,41 @@ impl TwoQubitWeylDecomposition { #[allow(non_snake_case)] #[getter] fn K1l(&self, py: Python) -> PyObject { - self.K1l.to_pyarray(py).into() + self.K1l.to_pyarray_bound(py).into() } #[allow(non_snake_case)] #[getter] fn K1r(&self, py: Python) -> PyObject { - self.K1r.to_pyarray(py).into() + self.K1r.to_pyarray_bound(py).into() } #[allow(non_snake_case)] #[getter] fn K2l(&self, py: Python) -> PyObject { - self.K2l.to_pyarray(py).into() + self.K2l.to_pyarray_bound(py).into() } #[allow(non_snake_case)] #[getter] fn K2r(&self, py: Python) -> PyObject { - self.K2r.to_pyarray(py).into() + self.K2r.to_pyarray_bound(py).into() } #[getter] fn unitary_matrix(&self, py: Python) -> PyObject { - self.unitary_matrix.to_pyarray(py).into() + self.unitary_matrix.to_pyarray_bound(py).into() } #[pyo3(signature = (euler_basis=None, simplify=false, atol=None))] fn circuit( &self, - euler_basis: Option<&str>, + euler_basis: Option, simplify: bool, atol: Option, ) -> PyResult { let euler_basis: EulerBasis = match euler_basis { - Some(basis) => EulerBasis::from_str(basis)?, + Some(basis) => EulerBasis::from_str(basis.deref())?, None => self.default_euler_basis, }; let target_1q_basis_list: Vec = vec![euler_basis]; @@ -1680,7 +1682,10 @@ impl TwoQubitBasisDecomposer { fn __getnewargs__(&self, py: Python) -> (String, PyObject, f64, &str, Option) { ( self.gate.clone(), - self.basis_decomposer.unitary_matrix.to_pyarray(py).into(), + self.basis_decomposer + .unitary_matrix + .to_pyarray_bound(py) + .into(), self.basis_fidelity, self.euler_basis.as_str(), self.pulse_optimize, @@ -1878,7 +1883,7 @@ impl TwoQubitBasisDecomposer { fn decomp0(py: Python, target: &TwoQubitWeylDecomposition) -> SmallVec<[PyObject; 2]> { decomp0_inner(target) .into_iter() - .map(|x| x.into_pyarray(py).into()) + .map(|x| x.into_pyarray_bound(py).into()) .collect() } @@ -1895,7 +1900,7 @@ impl TwoQubitBasisDecomposer { fn decomp1(&self, py: Python, target: &TwoQubitWeylDecomposition) -> SmallVec<[PyObject; 4]> { self.decomp1_inner(target) .into_iter() - .map(|x| x.into_pyarray(py).into()) + .map(|x| x.into_pyarray_bound(py).into()) .collect() } @@ -1920,7 +1925,7 @@ impl TwoQubitBasisDecomposer { ) -> SmallVec<[PyObject; 6]> { self.decomp2_supercontrolled_inner(target) .into_iter() - .map(|x| x.into_pyarray(py).into()) + .map(|x| x.into_pyarray_bound(py).into()) .collect() } @@ -1935,7 +1940,7 @@ impl TwoQubitBasisDecomposer { ) -> SmallVec<[PyObject; 8]> { self.decomp3_supercontrolled_inner(target) .into_iter() - .map(|x| x.into_pyarray(py).into()) + .map(|x| x.into_pyarray_bound(py).into()) .collect() } @@ -2046,7 +2051,7 @@ impl TwoQubitBasisDecomposer { } #[pymodule] -pub fn two_qubit_decompose(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn two_qubit_decompose(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(_num_basis_gates))?; m.add_class::()?; m.add_class::()?; diff --git a/crates/accelerate/src/utils.rs b/crates/accelerate/src/utils.rs index bc8a66568f9b..7beaa7233d19 100644 --- a/crates/accelerate/src/utils.rs +++ b/crates/accelerate/src/utils.rs @@ -25,7 +25,7 @@ pub enum SliceOrInt<'a> { // The order here defines the order the variants are tried in the FromPyObject` derivation. // `Int` is _much_ more common, so that should be first. Int(isize), - Slice(&'a PySlice), + Slice(Bound<'a, PySlice>), } /// Return indices that sort partially ordered data. @@ -49,12 +49,12 @@ pub fn eigenvalues(py: Python, unitary: PyReadonlyArray2>) -> PyObj .into_iter() .map(|x| Complex::::new(x.re, x.im)) .collect::>() - .into_pyarray(py) + .into_pyarray_bound(py) .into() } #[pymodule] -pub fn utils(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn utils(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(eigenvalues))?; Ok(()) } diff --git a/crates/accelerate/src/vf2_layout.rs b/crates/accelerate/src/vf2_layout.rs index fe361b079410..e84b2bfbfb8c 100644 --- a/crates/accelerate/src/vf2_layout.rs +++ b/crates/accelerate/src/vf2_layout.rs @@ -90,7 +90,7 @@ pub fn score_layout( .filter_map(edge_filter_map) .product() }; - fidelity *= if bit_list.len() < PARALLEL_THRESHOLD || !run_in_parallel { + fidelity *= if bit_list.len()? < PARALLEL_THRESHOLD || !run_in_parallel { bit_counts .iter() .enumerate() @@ -107,7 +107,7 @@ pub fn score_layout( } #[pymodule] -pub fn vf2_layout(_py: Python, m: &PyModule) -> PyResult<()> { +pub fn vf2_layout(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(score_layout))?; m.add_class::()?; Ok(()) diff --git a/crates/qasm2/src/expr.rs b/crates/qasm2/src/expr.rs index 59a3ebf3c221..f7faad0c6298 100644 --- a/crates/qasm2/src/expr.rs +++ b/crates/qasm2/src/expr.rs @@ -424,7 +424,7 @@ impl<'a> ExprParser<'a> { // going to have to acquire the GIL and call the Python object the user gave us right // now. We need to explicitly handle any exceptions that might occur from that. Python::with_gil(|py| { - let args = PyTuple::new( + let args = PyTuple::new_bound( py, exprs.iter().map(|x| { if let Expr::Constant(val) = x { diff --git a/crates/qasm2/src/lib.rs b/crates/qasm2/src/lib.rs index b71a30ef96a7..1731330d30c7 100644 --- a/crates/qasm2/src/lib.rs +++ b/crates/qasm2/src/lib.rs @@ -124,7 +124,7 @@ fn bytecode_from_file( /// output. The principal entry points for Python are :func:`bytecode_from_string` and /// :func:`bytecode_from_file`, which produce iterables of :class:`Bytecode` objects. #[pymodule] -fn _qasm2(py: Python<'_>, module: &PyModule) -> PyResult<()> { +fn _qasm2(module: &Bound<'_, PyModule>) -> PyResult<()> { module.add_class::()?; module.add_class::()?; module.add_class::()?; @@ -136,7 +136,10 @@ fn _qasm2(py: Python<'_>, module: &PyModule) -> PyResult<()> { module.add_class::()?; module.add_class::()?; module.add_class::()?; - module.add("QASM2ParseError", py.get_type::())?; + module.add( + "QASM2ParseError", + module.py().get_type_bound::(), + )?; module.add_function(wrap_pyfunction!(bytecode_from_string, module)?)?; module.add_function(wrap_pyfunction!(bytecode_from_file, module)?)?; Ok(()) diff --git a/crates/qasm3/src/build.rs b/crates/qasm3/src/build.rs index cc80aa4f762d..154cd391252a 100644 --- a/crates/qasm3/src/build.rs +++ b/crates/qasm3/src/build.rs @@ -143,7 +143,7 @@ impl BuilderState { let gate = self.symbols.gates.get(gate_id).ok_or_else(|| { QASM3ImporterError::new_err(format!("internal error: unknown gate {:?}", gate_id)) })?; - let params = PyTuple::new( + let params = PyTuple::new_bound( py, call.params() .as_ref() @@ -206,7 +206,7 @@ impl BuilderState { } } } - PyTuple::new(py, qubits.values()) + PyTuple::new_bound(py, qubits.values()) } else { // If there's no qargs (represented in the ASG with a `None` rather than an empty // vector), it's a barrier over all in-scope qubits, which is all qubits, unless we're diff --git a/crates/qasm3/src/circuit.rs b/crates/qasm3/src/circuit.rs index a2c39e754023..1293b22580b0 100644 --- a/crates/qasm3/src/circuit.rs +++ b/crates/qasm3/src/circuit.rs @@ -22,7 +22,7 @@ pub trait PyRegister { // or at a minimum // fn iter<'a>(&'a self, py: Python<'a>) -> ::pyo3::types::iter::PyListIterator<'a>; // but we can't use the former before Rust 1.75 and the latter before PyO3 0.21. - fn bit_list<'a>(&'a self, py: Python<'a>) -> &'a PyList; + fn bit_list<'a>(&'a self, py: Python<'a>) -> &Bound<'a, PyList>; } macro_rules! register_type { @@ -43,13 +43,13 @@ macro_rules! register_type { fn bit(&self, py: Python, index: usize) -> PyResult> { // Unfortunately, `PyList::get_item_unchecked` isn't usable with the stable ABI. self.items - .as_ref(py) + .bind(py) .get_item(index) .map(|item| item.into_py(py)) } - fn bit_list<'a>(&'a self, py: Python<'a>) -> &'a PyList { - self.items.as_ref(py) + fn bit_list<'a>(&'a self, py: Python<'a>) -> &Bound<'a, PyList> { + self.items.bind(py) } } @@ -106,9 +106,9 @@ impl PyGate { A: IntoPy>, { let args = args.into_py(py); - let received_num_params = args.as_ref(py).len(); + let received_num_params = args.bind(py).len(); if received_num_params == self.num_params { - self.constructor.call1(py, args.as_ref(py)) + self.constructor.call1(py, args.bind(py)) } else { Err(QASM3ImporterError::new_err(format!( "internal error: wrong number of params for {} (got {}, expected {})", @@ -143,11 +143,11 @@ impl PyGate { } } - fn __repr__<'py>(&self, py: Python<'py>) -> PyResult<&'py PyAny> { - PyString::new(py, "CustomGate(name={!r}, num_params={}, num_qubits={})").call_method1( + fn __repr__<'py>(&self, py: Python<'py>) -> PyResult> { + PyString::new_bound(py, "CustomGate(name={!r}, num_params={}, num_qubits={})").call_method1( "format", ( - PyString::new(py, &self.name), + PyString::new_bound(py, &self.name), self.num_params, self.num_qubits, ), @@ -156,7 +156,7 @@ impl PyGate { fn __reduce__(&self, py: Python) -> Py { ( - PyType::new::(py), + PyType::new_bound::(py), ( self.constructor.clone_ref(py), &self.name, @@ -188,27 +188,30 @@ pub struct PyCircuitModule { impl PyCircuitModule { /// Import the necessary components from `qiskit.circuit`. pub fn import(py: Python) -> PyResult { - let module = PyModule::import(py, "qiskit.circuit")?; + let module = PyModule::import_bound(py, "qiskit.circuit")?; Ok(Self { circuit: module .getattr("QuantumCircuit")? - .downcast::()? - .into_py(py), + .downcast_into::()? + .unbind(), qreg: module .getattr("QuantumRegister")? - .downcast::()? - .into_py(py), - qubit: module.getattr("Qubit")?.downcast::()?.into_py(py), + .downcast_into::()? + .unbind(), + qubit: module.getattr("Qubit")?.downcast_into::()?.unbind(), creg: module .getattr("ClassicalRegister")? - .downcast::()? - .into_py(py), - clbit: module.getattr("Clbit")?.downcast::()?.into_py(py), + .downcast_into::()? + .unbind(), + clbit: module.getattr("Clbit")?.downcast_into::()?.unbind(), circuit_instruction: module .getattr("CircuitInstruction")? - .downcast::()? - .into_py(py), - barrier: module.getattr("Barrier")?.downcast::()?.into_py(py), + .downcast_into::()? + .unbind(), + barrier: module + .getattr("Barrier")? + .downcast_into::()? + .unbind(), // Measure is a singleton, so just store the object. measure: module.getattr("Measure")?.call0()?.into_py(py), }) @@ -227,9 +230,10 @@ impl PyCircuitModule { let qreg = self.qreg.call1(py, (size, name.into_py(py)))?; Ok(PyQuantumRegister { items: qreg - .getattr(py, "_bits")? - .downcast::(py)? - .into_py(py), + .bind(py) + .getattr("_bits")? + .downcast_into::()? + .unbind(), object: qreg, }) } @@ -247,9 +251,10 @@ impl PyCircuitModule { let creg = self.creg.call1(py, (size, name.into_py(py)))?; Ok(PyClassicalRegister { items: creg - .getattr(py, "_bits")? - .downcast::(py)? - .into_py(py), + .bind(py) + .getattr("_bits")? + .downcast_into::()? + .unbind(), object: creg, }) } @@ -291,8 +296,8 @@ pub struct PyCircuit(Py); impl PyCircuit { /// Untyped access to the inner Python object. - pub fn inner<'a>(&'a self, py: Python<'a>) -> &'a PyAny { - self.0.as_ref(py) + pub fn inner<'a>(&'a self, py: Python<'a>) -> &Bound<'a, PyAny> { + self.0.bind(py) } pub fn add_qreg(&self, py: Python, qreg: &PyQuantumRegister) -> PyResult<()> { diff --git a/crates/qasm3/src/expr.rs b/crates/qasm3/src/expr.rs index 8fc4c8a8a534..d16bd53add05 100644 --- a/crates/qasm3/src/expr.rs +++ b/crates/qasm3/src/expr.rs @@ -110,7 +110,7 @@ struct BroadcastQubitsIter<'py> { } impl<'py> Iterator for BroadcastQubitsIter<'py> { - type Item = &'py PyTuple; + type Item = Bound<'py, PyTuple>; fn next(&mut self) -> Option { if self.offset >= self.len { @@ -122,7 +122,10 @@ impl<'py> Iterator for BroadcastQubitsIter<'py> { BroadcastItem::Register(bits) => bits[offset].clone_ref(self.py), }; self.offset += 1; - Some(PyTuple::new(self.py, self.items.iter().map(to_scalar))) + Some(PyTuple::new_bound( + self.py, + self.items.iter().map(to_scalar), + )) } fn size_hint(&self) -> (usize, Option) { @@ -140,7 +143,7 @@ struct BroadcastMeasureIter<'a, 'py> { } impl<'a, 'py> Iterator for BroadcastMeasureIter<'a, 'py> { - type Item = (&'py PyTuple, &'py PyTuple); + type Item = (Bound<'py, PyTuple>, Bound<'py, PyTuple>); fn next(&mut self) -> Option { if self.offset >= self.len { @@ -153,8 +156,8 @@ impl<'a, 'py> Iterator for BroadcastMeasureIter<'a, 'py> { }; self.offset += 1; Some(( - PyTuple::new(self.py, &[to_scalar(self.qarg)]), - PyTuple::new(self.py, &[to_scalar(self.carg)]), + PyTuple::new_bound(self.py, &[to_scalar(self.qarg)]), + PyTuple::new_bound(self.py, &[to_scalar(self.carg)]), )) } @@ -321,7 +324,7 @@ pub fn broadcast_qubits<'a, 'py, T>( our_symbols: &PySymbolTable, ast_symbols: &SymbolTable, qargs: T, -) -> PyResult> +) -> PyResult>> where T: IntoIterator + 'a, { @@ -358,7 +361,7 @@ pub fn broadcast_measure<'a, 'py>( py: Python<'py>, qarg: &'a BroadcastItem, carg: &'a BroadcastItem, -) -> PyResult + 'a> +) -> PyResult, Bound<'py, PyTuple>)> + 'a> where 'py: 'a, { diff --git a/crates/qasm3/src/lib.rs b/crates/qasm3/src/lib.rs index 0db8317ba6a1..12ff4c88a558 100644 --- a/crates/qasm3/src/lib.rs +++ b/crates/qasm3/src/lib.rs @@ -16,6 +16,7 @@ mod error; mod expr; use std::ffi::OsString; +use std::ops::Deref; use std::path::{Path, PathBuf}; use hashbrown::HashMap; @@ -24,6 +25,7 @@ use pyo3::prelude::*; use pyo3::types::{PyModule, PyTuple}; use oq3_semantics::syntax_to_semantics::parse_source_string; +use pyo3::pybacked::PyBackedStr; use crate::error::QASM3ImporterError; @@ -58,14 +60,15 @@ const STDGATES_INC_CUSTOM_GATES_ATTR: &str = "STDGATES_INC_GATES"; #[pyfunction] #[pyo3(pass_module, signature = (source, /, *, custom_gates=None, include_path=None))] pub fn loads( - module: &PyModule, + module: &Bound, py: Python, source: String, custom_gates: Option>, include_path: Option>, ) -> PyResult { let default_include_path = || -> PyResult> { - Ok(vec![Path::new(module.filename()?) + let filename: PyBackedStr = module.filename()?.try_into()?; + Ok(vec![Path::new(filename.deref()) .parent() .unwrap() .join(["qasm", "libs", "dummy"].iter().collect::()) @@ -130,34 +133,35 @@ pub fn loads( signature = (pathlike_or_filelike, /, *, custom_gates=None, include_path=None), )] pub fn load( - module: &PyModule, + module: &Bound, py: Python, - pathlike_or_filelike: &PyAny, + pathlike_or_filelike: &Bound, custom_gates: Option>, include_path: Option>, ) -> PyResult { - let source = - if pathlike_or_filelike.is_instance(PyModule::import(py, "io")?.getattr("TextIOBase")?)? { - pathlike_or_filelike - .call_method0("read")? - .extract::()? - } else { - let path = PyModule::import(py, "os")? - .getattr("fspath")? - .call1((pathlike_or_filelike,))? - .extract::()?; - ::std::fs::read_to_string(&path).map_err(|err| { - QASM3ImporterError::new_err(format!("failed to read file '{:?}': {:?}", &path, err)) - })? - }; + let source = if pathlike_or_filelike + .is_instance(&PyModule::import_bound(py, "io")?.getattr("TextIOBase")?)? + { + pathlike_or_filelike + .call_method0("read")? + .extract::()? + } else { + let path = PyModule::import_bound(py, "os")? + .getattr("fspath")? + .call1((pathlike_or_filelike,))? + .extract::()?; + ::std::fs::read_to_string(&path).map_err(|err| { + QASM3ImporterError::new_err(format!("failed to read file '{:?}': {:?}", &path, err)) + })? + }; loads(module, py, source, custom_gates, include_path) } /// Create a suitable sequence for use with the ``custom_gates`` of :func:`load` and :func:`loads`, /// as a Python object on the Python heap, so we can re-use it, and potentially expose it has a /// data attribute to users. -fn stdgates_inc_gates(py: Python) -> PyResult<&PyTuple> { - let library = PyModule::import(py, "qiskit.circuit.library")?; +fn stdgates_inc_gates(py: Python) -> PyResult> { + let library = PyModule::import_bound(py, "qiskit.circuit.library")?; let stdlib_gate = |qiskit_class, name, num_params, num_qubits| -> PyResult> { Ok(circuit::PyGate::new( py, @@ -168,7 +172,7 @@ fn stdgates_inc_gates(py: Python) -> PyResult<&PyTuple> { ) .into_py(py)) }; - Ok(PyTuple::new( + Ok(PyTuple::new_bound( py, vec![ stdlib_gate("PhaseGate", "p", 1, 1)?, @@ -210,10 +214,13 @@ fn stdgates_inc_gates(py: Python) -> PyResult<&PyTuple> { /// Internal module supplying the OpenQASM 3 import capabilities. The entries in it should largely /// be re-exposed directly to public Python space. #[pymodule] -fn _qasm3(py: Python<'_>, module: &PyModule) -> PyResult<()> { +fn _qasm3(module: &Bound) -> PyResult<()> { module.add_function(wrap_pyfunction!(loads, module)?)?; module.add_function(wrap_pyfunction!(load, module)?)?; module.add_class::()?; - module.add(STDGATES_INC_CUSTOM_GATES_ATTR, stdgates_inc_gates(py)?)?; + module.add( + STDGATES_INC_CUSTOM_GATES_ATTR, + stdgates_inc_gates(module.py())?, + )?; Ok(()) } diff --git a/qiskit/primitives/__init__.py b/qiskit/primitives/__init__.py index dfc315676e92..0a36dbbb0bd3 100644 --- a/qiskit/primitives/__init__.py +++ b/qiskit/primitives/__init__.py @@ -110,7 +110,7 @@ :class:`~BaseSamplerV2` is a primitive that samples outputs of quantum circuits. Following construction, a sampler is used by calling its :meth:`~.BaseSamplerV2.run` method -with a list of pubs (Primitive Unified Blocks). Each pub contains values that, together, +with a list of pubs (Primitive Unified Blocs). Each pub contains values that, together, define a computational unit of work for the sampler to complete: * A single :class:`~qiskit.circuit.QuantumCircuit`, possibly parameterized. diff --git a/qiskit/providers/fake_provider/generic_backend_v2.py b/qiskit/providers/fake_provider/generic_backend_v2.py index 3776211cc08c..b8f40ff1a275 100644 --- a/qiskit/providers/fake_provider/generic_backend_v2.py +++ b/qiskit/providers/fake_provider/generic_backend_v2.py @@ -62,8 +62,8 @@ # Fallback values for gates with unknown noise default ranges. _NOISE_DEFAULTS_FALLBACK = { - "1-q": (3e-8, 6e-8, 9e-5, 1e-4), - "multi-q": (8e-8, 9e-7, 1e-5, 5e-3), + "1-q": (2.997e-08, 5.994e-08, 9e-5, 1e-4), + "multi-q": (7.992e-08, 8.99988e-07, 5e-3), } # Ranges to sample qubit properties from. diff --git a/qiskit/transpiler/__init__.py b/qiskit/transpiler/__init__.py index 6e1522e81fa9..12d9b9056b80 100644 --- a/qiskit/transpiler/__init__.py +++ b/qiskit/transpiler/__init__.py @@ -799,7 +799,7 @@ at the output. In order to highlight this, we run a GHZ circuit 100 times, using a "bad" (disconnected) -`initial_layout`: +``initial_layout`` in a heavy hex coupling map: .. plot:: @@ -816,18 +816,22 @@ import matplotlib.pyplot as plt from qiskit import QuantumCircuit, transpile from qiskit.providers.fake_provider import GenericBackendV2 - backend = GenericBackendV2(16) + from qiskit.transpiler import CouplingMap + + coupling_map = CouplingMap.from_heavy_hex(3) + backend = GenericBackendV2(coupling_map.size(), coupling_map=coupling_map) ghz = QuantumCircuit(15) ghz.h(0) ghz.cx(0, range(1, 15)) depths = [] - for _ in range(100): + for i in range(100): depths.append( transpile( ghz, backend, + seed_transpiler=i, layout_method='trivial' # Fixed layout mapped in circuit order ).depth() ) diff --git a/qiskit/transpiler/passes/scheduling/__init__.py b/qiskit/transpiler/passes/scheduling/__init__.py index 6283faff0001..0d120911b06f 100644 --- a/qiskit/transpiler/passes/scheduling/__init__.py +++ b/qiskit/transpiler/passes/scheduling/__init__.py @@ -20,7 +20,7 @@ from .padding import PadDelay, PadDynamicalDecoupling from .alignments import InstructionDurationCheck, ValidatePulseGates, ConstrainedReschedule -# For backward compability +# For backward compatibility from . import alignments as instruction_alignments # TODO Deprecated pass. Will be removed after deprecation period. diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py index 9ee0f4988b4a..ee2c9160c197 100644 --- a/qiskit/transpiler/passes/scheduling/alap.py +++ b/qiskit/transpiler/passes/scheduling/alap.py @@ -32,8 +32,7 @@ class ALAPSchedule(BaseSchedulerTransform): "Instead, use :class:`~.ALAPScheduleAnalysis`, which is an " "analysis pass that requires a padding pass to later modify the circuit." ), - since="0.21.0", - pending=True, + since="1.1.0", ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py index 668d65f6abd5..adbf5d89dd25 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py +++ b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py @@ -95,8 +95,7 @@ class AlignMeasures(TransformationPass): "Instead, use :class:`~.ConstrainedReschedule`, which performs the same function " "but also supports aligning to additional timing constraints." ), - since="0.21.0", - pending=True, + since="1.1.0", ) def __init__(self, alignment: int = 1): """Create new pass. diff --git a/qiskit/transpiler/passes/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/asap.py index cebc32af71a8..0ea56bb67468 100644 --- a/qiskit/transpiler/passes/scheduling/asap.py +++ b/qiskit/transpiler/passes/scheduling/asap.py @@ -38,8 +38,7 @@ class ASAPSchedule(BaseSchedulerTransform): "Instead, use :class:`~.ASAPScheduleAnalysis`, which is an " "analysis pass that requires a padding pass to later modify the circuit." ), - since="0.21.0", - pending=True, + since="1.1.0", ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py index 6883e4cdc168..5b84b529e453 100644 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -109,8 +109,7 @@ def uhrig_pulse_location(k): "Instead, use :class:`~.PadDynamicalDecoupling`, which performs the same " "function but requires scheduling and alignment analysis passes to run prior to it." ), - since="0.21.0", - pending=True, + since="1.1.0", ) def __init__( self, durations, dd_sequence, qubits=None, spacing=None, skip_reset_qubits=True, target=None diff --git a/qiskit/visualization/counts_visualization.py b/qiskit/visualization/counts_visualization.py index 546a027b5b79..6d93ff6bfac0 100644 --- a/qiskit/visualization/counts_visualization.py +++ b/qiskit/visualization/counts_visualization.py @@ -58,7 +58,7 @@ def _is_deprecated_data_format(data) -> bool: def plot_histogram( data, - figsize=(7, 5), + figsize=None, color=None, number_to_keep=None, sort="asc", @@ -296,7 +296,7 @@ def _plotting_core( # Set bar colors if color is None: - color = ["#648fff", "#dc267f", "#785ef0", "#ffb000", "#fe6100"] + color = plt.rcParams["axes.prop_cycle"].by_key()["color"] elif isinstance(color, str): color = [color] @@ -350,7 +350,7 @@ def _plotting_core( label = None bar_center = (width / 2) * (length - 1) ax.set_xticks(all_inds[item] + bar_center) - ax.set_xticklabels(labels_dict.keys(), fontsize=14, rotation=70) + ax.set_xticklabels(labels_dict.keys(), rotation=70, ha="right", rotation_mode="anchor") # attach some text labels if bar_labels: for rect in rects: @@ -391,8 +391,6 @@ def _plotting_core( ax.invert_xaxis() ax.yaxis.set_major_locator(MaxNLocator(5)) - for tick in ax.yaxis.get_major_ticks(): - tick.label1.set_fontsize(14) plt.grid(which="major", axis="y", zorder=0, linestyle="--") if title: plt.title(title) @@ -404,7 +402,6 @@ def _plotting_core( ncol=1, borderaxespad=0, frameon=True, - fontsize=12, ) if fig: matplotlib_close_if_inline(fig) diff --git a/releasenotes/notes/histogram-style-03807965c3cc2e8a.yaml b/releasenotes/notes/histogram-style-03807965c3cc2e8a.yaml new file mode 100644 index 000000000000..5904885c430b --- /dev/null +++ b/releasenotes/notes/histogram-style-03807965c3cc2e8a.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - | + Removes the hard-coding of style options for :func:`~.plot_histogram`. This allows + Matplotlib style files to be faithfully applied to the figures. Users looking to go + beyond the defaults set by Matplotlib can make their own style files, or pass a + Matplotlib ``Axes`` object to ``plot_histogram`` and post-apply any customizations. diff --git a/releasenotes/notes/removed_deprecated_0.21-741d08a01a7ed527.yaml b/releasenotes/notes/removed_deprecated_0.21-741d08a01a7ed527.yaml new file mode 100644 index 000000000000..ebfbc01a64a3 --- /dev/null +++ b/releasenotes/notes/removed_deprecated_0.21-741d08a01a7ed527.yaml @@ -0,0 +1,21 @@ +--- +deprecations_transpiler: + - | + The transpilation pass ``qiskit.transpiler.passes.ALAPSchedule`` is now deprecated. It was pending for deprecation + since Qiskit 0.37 (with Terra 0.21), released on June 2022. The pass is replaced by + :class:`~.transpiler.passes.ALAPScheduleAnalysis`, which is an + analysis pass. + - | + The transpilation pass ``qiskit.transpiler.passes.ASAPSchedule`` is now deprecated. It was pending for deprecation + since Qiskit 0.37 (with Terra 0.21), released on June 2022. It has been superseded by + :class:`~.ASAPScheduleAnalysis` and the new scheduling workflow. + - | + The transpilation pass ``qiskit.transpiler.passes.DynamicalDecoupling`` is now deprecated. It was pending for deprecation + since Qiskit 0.37 (with Terra 0.21), released on June 2022. + Instead, use :class:`~.transpiler.passes.PadDynamicalDecoupling`, which performs the same + function but requires scheduling and alignment analysis passes to run prior to it. + - | + The transpilation pass ``qiskit.transpiler.passes.AlignMeasures`` is now deprecated. It was pending for deprecation + since Qiskit 0.37 (with Terra 0.21), released on June 2022. + Instead, use :class:`~.ConstrainedReschedule`, which performs the same function + and also supports aligning to additional timing constraints. diff --git a/test/python/providers/fake_provider/test_generic_backend_v2.py b/test/python/providers/fake_provider/test_generic_backend_v2.py index 4b20b5fe8abd..b4fbe944c332 100644 --- a/test/python/providers/fake_provider/test_generic_backend_v2.py +++ b/test/python/providers/fake_provider/test_generic_backend_v2.py @@ -117,19 +117,20 @@ def test_duration_defaults(self): basis_gates = ["cx", "id", "rz", "sx", "x", "sdg", "rxx"] expected_durations = { - "cx": (8e-8, 9e-7), - "id": (3e-8, 6e-8), + "cx": (7.992e-08, 8.99988e-07), + "id": (2.997e-08, 5.994e-08), "rz": (0.0, 0.0), - "sx": (3e-8, 6e-8), - "x": (3e-8, 6e-8), - "measure": (7e-7, 1.5e-6), - "sdg": (3e-8, 6e-8), - "rxx": (8e-8, 9e-7), + "sx": (2.997e-08, 5.994e-08), + "x": (2.997e-08, 5.994e-08), + "measure": (6.99966e-07, 1.500054e-06), + "sdg": (2.997e-08, 5.994e-08), + "rxx": (7.992e-08, 8.99988e-07), } - target = GenericBackendV2(num_qubits=2, basis_gates=basis_gates).target - for inst in target: - for qargs in target.qargs_for_operation_name(inst): - duration = target[inst][qargs].duration - if inst not in ["delay", "reset"]: - self.assertGreaterEqual(duration, expected_durations[inst][0]) - self.assertLessEqual(duration, expected_durations[inst][1]) + for _ in range(20): + target = GenericBackendV2(num_qubits=2, basis_gates=basis_gates).target + for inst in target: + for qargs in target.qargs_for_operation_name(inst): + duration = target[inst][qargs].duration + if inst not in ["delay", "reset"]: + self.assertGreaterEqual(duration, expected_durations[inst][0]) + self.assertLessEqual(duration, expected_durations[inst][1]) diff --git a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py index d15e2f0a6978..c1df8adbad2c 100644 --- a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py +++ b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py @@ -46,12 +46,13 @@ def setUp(self): self.time_conversion_pass = TimeUnitConversion(inst_durations=instruction_durations) # reproduce old behavior of 0.20.0 before #7655 # currently default write latency is 0 - self.scheduling_pass = ALAPSchedule( - durations=instruction_durations, - clbit_write_latency=1600, - conditional_latency=0, - ) - self.align_measure_pass = AlignMeasures(alignment=16) + with self.assertWarns(DeprecationWarning): + self.scheduling_pass = ALAPSchedule( + durations=instruction_durations, + clbit_write_latency=1600, + conditional_latency=0, + ) + self.align_measure_pass = AlignMeasures(alignment=16) def test_t1_experiment_type(self): """Test T1 experiment type circuit. diff --git a/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py b/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py index fea814d1b5c9..417ff9b42212 100644 --- a/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py +++ b/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py @@ -43,10 +43,12 @@ def test_alap_agree_with_reverse_asap_reverse(self): [("h", 0, 200), ("cx", [0, 1], 700), ("measure", None, 1000)] ) - pm = PassManager(ALAPSchedule(durations)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(ALAPSchedule(durations)) alap_qc = pm.run(qc) - pm = PassManager(ASAPSchedule(durations)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(ASAPSchedule(durations)) new_qc = pm.run(qc.reverse_ops()) new_qc = new_qc.reverse_ops() new_qc.name = new_qc.name @@ -83,7 +85,8 @@ def test_classically_controlled_gate_after_measure(self, schedule_pass): qc.x(1).c_if(0, True) durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - pm = PassManager(schedule_pass(durations)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -123,7 +126,8 @@ def test_measure_after_measure(self, schedule_pass): qc.measure(1, 0) durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - pm = PassManager(schedule_pass(durations)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -170,7 +174,8 @@ def test_c_if_on_different_qubits(self, schedule_pass): qc.x(2).c_if(0, True) durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - pm = PassManager(schedule_pass(durations)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -210,7 +215,8 @@ def test_shorter_measure_after_measure(self, schedule_pass): qc.measure(1, 0) durations = InstructionDurations([("measure", [0], 1000), ("measure", [1], 700)]) - pm = PassManager(schedule_pass(durations)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -253,7 +259,8 @@ def test_measure_after_c_if(self, schedule_pass): qc.measure(2, 0) durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - pm = PassManager(schedule_pass(durations)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(schedule_pass(durations)) scheduled = pm.run(qc) expected = QuantumCircuit(3, 1) @@ -307,7 +314,8 @@ def test_parallel_gate_different_length(self): durations = InstructionDurations( [("x", [0], 200), ("x", [1], 400), ("measure", None, 1000)] ) - pm = PassManager(ALAPSchedule(durations)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(ALAPSchedule(durations)) qc_alap = pm.run(qc) alap_expected = QuantumCircuit(2, 2) @@ -319,7 +327,8 @@ def test_parallel_gate_different_length(self): self.assertEqual(qc_alap, alap_expected) - pm = PassManager(ASAPSchedule(durations)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(ASAPSchedule(durations)) qc_asap = pm.run(qc) asap_expected = QuantumCircuit(2, 2) @@ -371,7 +380,8 @@ def test_parallel_gate_different_length_with_barrier(self): durations = InstructionDurations( [("x", [0], 200), ("x", [1], 400), ("measure", None, 1000)] ) - pm = PassManager(ALAPSchedule(durations)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(ALAPSchedule(durations)) qc_alap = pm.run(qc) alap_expected = QuantumCircuit(2, 2) @@ -384,7 +394,8 @@ def test_parallel_gate_different_length_with_barrier(self): self.assertEqual(qc_alap, alap_expected) - pm = PassManager(ASAPSchedule(durations)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(ASAPSchedule(durations)) qc_asap = pm.run(qc) asap_expected = QuantumCircuit(2, 2) @@ -446,8 +457,9 @@ def test_measure_after_c_if_on_edge_locking(self): durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) # lock at the end edge - actual_asap = PassManager(ASAPSchedule(durations, clbit_write_latency=1000)).run(qc) - actual_alap = PassManager(ALAPSchedule(durations, clbit_write_latency=1000)).run(qc) + with self.assertWarns(DeprecationWarning): + actual_asap = PassManager(ASAPSchedule(durations, clbit_write_latency=1000)).run(qc) + actual_alap = PassManager(ALAPSchedule(durations, clbit_write_latency=1000)).run(qc) # start times of 2nd measure depends on ASAP/ALAP expected_asap = QuantumCircuit(3, 1) @@ -495,12 +507,13 @@ def test_active_reset_circuit(self, write_lat, cond_lat): qc.x(0).c_if(0, 1) durations = InstructionDurations([("x", None, 100), ("measure", None, 1000)]) - actual_asap = PassManager( - ASAPSchedule(durations, clbit_write_latency=write_lat, conditional_latency=cond_lat) - ).run(qc) - actual_alap = PassManager( - ALAPSchedule(durations, clbit_write_latency=write_lat, conditional_latency=cond_lat) - ).run(qc) + with self.assertWarns(DeprecationWarning): + actual_asap = PassManager( + ASAPSchedule(durations, clbit_write_latency=write_lat, conditional_latency=cond_lat) + ).run(qc) + actual_alap = PassManager( + ALAPSchedule(durations, clbit_write_latency=write_lat, conditional_latency=cond_lat) + ).run(qc) expected = QuantumCircuit(1, 1) expected.measure(0, 0) @@ -618,12 +631,13 @@ def test_random_complicated_circuit(self): [("x", None, 100), ("measure", None, 1000), ("cx", None, 200)] ) - actual_asap = PassManager( - ASAPSchedule(durations, clbit_write_latency=100, conditional_latency=200) - ).run(qc) - actual_alap = PassManager( - ALAPSchedule(durations, clbit_write_latency=100, conditional_latency=200) - ).run(qc) + with self.assertWarns(DeprecationWarning): + actual_asap = PassManager( + ASAPSchedule(durations, clbit_write_latency=100, conditional_latency=200) + ).run(qc) + actual_alap = PassManager( + ALAPSchedule(durations, clbit_write_latency=100, conditional_latency=200) + ).run(qc) expected_asap = QuantumCircuit(3, 1) expected_asap.delay(100, 0) @@ -712,7 +726,8 @@ def test_dag_introduces_extra_dependency_between_conditionals(self): qc.x(1).c_if(0, True) durations = InstructionDurations([("x", None, 160)]) - pm = PassManager(ASAPSchedule(durations)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(ASAPSchedule(durations)) scheduled = pm.run(qc) expected = QuantumCircuit(2, 1) @@ -735,7 +750,8 @@ def test_respect_target_instruction_constraints(self, schedule_pass): qc = QuantumCircuit(2) qc.x(1) - pm = PassManager(schedule_pass(target=target)) + with self.assertWarns(DeprecationWarning): + pm = PassManager(schedule_pass(target=target)) scheduled = pm.run(qc) expected = QuantumCircuit(2) @@ -768,32 +784,36 @@ def test_dd_respect_target_instruction_constraints(self): # delays are not supported # No DD instructions nor delays are padded due to no delay support in the target - pm_xx = PassManager( - [ - ALAPSchedule(target=target), - DynamicalDecoupling(durations=None, dd_sequence=[XGate(), XGate()], target=target), - ] - ) - scheduled = pm_xx.run(qc) - self.assertEqual(qc, scheduled) - - # Fails since Y is not supported in the target - with self.assertRaises(TranspilerError): - PassManager( + with self.assertWarns(DeprecationWarning): + pm_scheduler = PassManager( [ ALAPSchedule(target=target), DynamicalDecoupling( - durations=None, - dd_sequence=[XGate(), YGate(), XGate(), YGate()], - target=target, + durations=None, dd_sequence=[XGate(), XGate()], target=target ), ] ) + scheduled = pm_scheduler.run(qc) + self.assertEqual(qc, scheduled) + + # Fails since Y is not supported in the target + with self.assertWarns(DeprecationWarning): + with self.assertRaises(TranspilerError): + PassManager( + [ + ALAPSchedule(target=target), + DynamicalDecoupling( + durations=None, + dd_sequence=[XGate(), YGate(), XGate(), YGate()], + target=target, + ), + ] + ) # Add delay support to the target target.add_instruction(Delay(Parameter("t")), {(q,): None for q in range(3)}) # No error but no DD on qubit 2 (just delay is padded) since X is not supported on it - scheduled = pm_xx.run(qc) + scheduled = pm_scheduler.run(qc) expected = QuantumCircuit(3) expected.delay(1000, [2]) diff --git a/test/visual/mpl/graph/references/histogram.png b/test/visual/mpl/graph/references/histogram.png index 1d11b0d110ce..3fa51ec644ed 100644 Binary files a/test/visual/mpl/graph/references/histogram.png and b/test/visual/mpl/graph/references/histogram.png differ diff --git a/test/visual/mpl/graph/references/histogram_2_sets_with_rest.png b/test/visual/mpl/graph/references/histogram_2_sets_with_rest.png index ab02566c1561..fe11f5811931 100644 Binary files a/test/visual/mpl/graph/references/histogram_2_sets_with_rest.png and b/test/visual/mpl/graph/references/histogram_2_sets_with_rest.png differ diff --git a/test/visual/mpl/graph/references/histogram_color.png b/test/visual/mpl/graph/references/histogram_color.png index 48890f468c7f..34f7d449616a 100644 Binary files a/test/visual/mpl/graph/references/histogram_color.png and b/test/visual/mpl/graph/references/histogram_color.png differ diff --git a/test/visual/mpl/graph/references/histogram_desc_value_sort.png b/test/visual/mpl/graph/references/histogram_desc_value_sort.png index 4a963ff40212..b119ae2deed4 100644 Binary files a/test/visual/mpl/graph/references/histogram_desc_value_sort.png and b/test/visual/mpl/graph/references/histogram_desc_value_sort.png differ diff --git a/test/visual/mpl/graph/references/histogram_hamming.png b/test/visual/mpl/graph/references/histogram_hamming.png index 12ed9e6418e2..95bfe0ab1049 100644 Binary files a/test/visual/mpl/graph/references/histogram_hamming.png and b/test/visual/mpl/graph/references/histogram_hamming.png differ diff --git a/test/visual/mpl/graph/references/histogram_legend.png b/test/visual/mpl/graph/references/histogram_legend.png index daa61cdeeb43..77fbe9c5dc0f 100644 Binary files a/test/visual/mpl/graph/references/histogram_legend.png and b/test/visual/mpl/graph/references/histogram_legend.png differ diff --git a/test/visual/mpl/graph/references/histogram_multiple_colors.png b/test/visual/mpl/graph/references/histogram_multiple_colors.png index c1ecd9aef02e..7edbfc07bab5 100644 Binary files a/test/visual/mpl/graph/references/histogram_multiple_colors.png and b/test/visual/mpl/graph/references/histogram_multiple_colors.png differ diff --git a/test/visual/mpl/graph/references/histogram_title.png b/test/visual/mpl/graph/references/histogram_title.png index cf72f4181c12..c4c6859c8f2e 100644 Binary files a/test/visual/mpl/graph/references/histogram_title.png and b/test/visual/mpl/graph/references/histogram_title.png differ diff --git a/test/visual/mpl/graph/references/histogram_value_sort.png b/test/visual/mpl/graph/references/histogram_value_sort.png index df5328ef884c..e44d08e0d9f9 100644 Binary files a/test/visual/mpl/graph/references/histogram_value_sort.png and b/test/visual/mpl/graph/references/histogram_value_sort.png differ diff --git a/test/visual/mpl/graph/references/histogram_with_rest.png b/test/visual/mpl/graph/references/histogram_with_rest.png index baecf1b96bd4..69633c1e4fdb 100644 Binary files a/test/visual/mpl/graph/references/histogram_with_rest.png and b/test/visual/mpl/graph/references/histogram_with_rest.png differ