diff --git a/Cargo.lock b/Cargo.lock index 133299e5a34a..5c787e5e204d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -839,9 +839,9 @@ dependencies = [ [[package]] name = "numpy" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb929bc0da91a4d85ed6c0a84deaa53d411abfb387fc271124f91bf6b89f14e" +checksum = "b94caae805f998a07d33af06e6a3891e38556051b8045c615470a71590e13e78" dependencies = [ "libc", "ndarray", @@ -849,7 +849,7 @@ dependencies = [ "num-integer", "num-traits", "pyo3", - "rustc-hash", + "rustc-hash 2.1.0", ] [[package]] @@ -916,7 +916,7 @@ dependencies = [ "oq3_lexer", "oq3_parser", "rowan", - "rustc-hash", + "rustc-hash 1.1.0", "rustversion", "smol_str", "triomphe", @@ -1082,9 +1082,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.22.6" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" +checksum = "57fe09249128b3173d092de9523eaa75136bf7ba85e0d69eca241c7939c933cc" dependencies = [ "cfg-if", "hashbrown 0.14.5", @@ -1105,9 +1105,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.6" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" +checksum = "1cd3927b5a78757a0d71aa9dff669f903b1eb64b54142a9bd9f757f8fde65fd7" dependencies = [ "once_cell", "target-lexicon", @@ -1115,9 +1115,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.22.6" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" +checksum = "dab6bb2102bd8f991e7749f130a70d05dd557613e39ed2deeee8e9ca0c4d548d" dependencies = [ "libc", "pyo3-build-config", @@ -1125,9 +1125,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.6" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" +checksum = "91871864b353fd5ffcb3f91f2f703a22a9797c91b9ab497b1acac7b07ae509c7" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1137,9 +1137,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.22.6" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" +checksum = "43abc3b80bc20f3facd86cd3c60beed58c3e2aa26213f3cda368de39c60a27e4" dependencies = [ "heck", "proc-macro2", @@ -1386,7 +1386,7 @@ dependencies = [ "countme", "hashbrown 0.14.5", "memoffset", - "rustc-hash", + "rustc-hash 1.1.0", "text-size", ] @@ -1396,6 +1396,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + [[package]] name = "rustiq-core" version = "0.0.10" diff --git a/Cargo.toml b/Cargo.toml index 35fa7cb3c39d..14689faf7de8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ hashbrown.version = "0.14.5" num-bigint = "0.4" num-complex = "0.4" ndarray = "0.15" -numpy = "0.22.1" +numpy = "0.23" smallvec = "1.13" thiserror = "1.0" rustworkx-core = "0.15" @@ -34,7 +34,7 @@ rayon = "1.10" # distributions). We only activate that feature when building the C extension module; we still need # it disabled for Rust-only tests to avoid linker errors with it not being loaded. See # https://pyo3.rs/main/features#extension-module for more. -pyo3 = { version = "0.22.6", features = ["abi3-py39"] } +pyo3 = { version = "0.23", features = ["abi3-py39"] } # These are our own crates. qiskit-accelerate = { path = "crates/accelerate" } diff --git a/crates/accelerate/src/basis/basis_translator/compose_transforms.rs b/crates/accelerate/src/basis/basis_translator/compose_transforms.rs index b1366c30bf2f..5eb7e80ff47b 100644 --- a/crates/accelerate/src/basis/basis_translator/compose_transforms.rs +++ b/crates/accelerate/src/basis/basis_translator/compose_transforms.rs @@ -106,7 +106,7 @@ pub(super) fn compose_transforms<'a>( for (node, params) in nodes_to_replace { let param_mapping: HashMap = equiv_params .iter() - .map(|x| ParameterUuid::from_parameter(x.to_object(py).bind(py))) + .map(|x| ParameterUuid::from_parameter(&x.into_pyobject(py).unwrap())) .zip(params) .map(|(uuid, param)| -> PyResult<(ParameterUuid, Param)> { Ok((uuid?, param.clone_ref(py))) diff --git a/crates/accelerate/src/basis/basis_translator/mod.rs b/crates/accelerate/src/basis/basis_translator/mod.rs index c900f80beff4..d023bc31d3d3 100644 --- a/crates/accelerate/src/basis/basis_translator/mod.rs +++ b/crates/accelerate/src/basis/basis_translator/mod.rs @@ -224,7 +224,7 @@ fn extract_basis( unreachable!("Control flow operation is not an instance of PyInstruction.") }; let inst_bound = inst.instruction.bind(py); - for block in inst_bound.getattr("blocks")?.iter()? { + for block in inst_bound.getattr("blocks")?.try_iter()? { recurse_circuit(py, block?, basis, min_qubits)?; } } @@ -255,7 +255,7 @@ fn extract_basis( if inst.op.control_flow() { let operation_ob = instruction_object.getattr(intern!(py, "operation"))?; let blocks = operation_ob.getattr("blocks")?; - for block in blocks.iter()? { + for block in blocks.try_iter()? { recurse_circuit(py, block?, basis, min_qubits)?; } } @@ -326,7 +326,7 @@ fn extract_basis_target( let bound_inst = op.instruction.bind(py); // Use python side extraction instead of the Rust method `op.blocks` due to // required usage of a python-space method `QuantumCircuit.has_calibration_for`. - let blocks = bound_inst.getattr("blocks")?.iter()?; + let blocks = bound_inst.getattr("blocks")?.try_iter()?; for block in blocks { extract_basis_target_circ( &block?, @@ -403,7 +403,7 @@ fn extract_basis_target_circ( unreachable!("Control flow op is not a control flow op. But control_flow is `true`") }; let bound_inst = op.instruction.bind(py); - let blocks = bound_inst.getattr("blocks")?.iter()?; + let blocks = bound_inst.getattr("blocks")?.try_iter()?; for block in blocks { extract_basis_target_circ( &block?, @@ -443,7 +443,7 @@ fn apply_translation( let mut flow_blocks = vec![]; let bound_obj = control_op.instruction.bind(py); let blocks = bound_obj.getattr("blocks")?; - for block in blocks.iter()? { + for block in blocks.try_iter()? { let block = block?; let dag_block: DAGCircuit = circuit_to_dag(py, block.extract()?, true, None, None)?; @@ -667,7 +667,7 @@ fn replace_node( let parameter_map = target_params .iter() .zip(node.params_view()) - .into_py_dict_bound(py); + .into_py_dict(py)?; for inner_index in target_dag.topological_op_nodes()? { let inner_node = &target_dag.dag()[inner_index].unwrap_operation(); let old_qargs = dag.get_qargs(node.qubits); @@ -702,7 +702,7 @@ fn replace_node( if let Param::ParameterExpression(param_obj) = param { let bound_param = param_obj.bind(py); let exp_params = param.iter_parameters(py)?; - let bind_dict = PyDict::new_bound(py); + let bind_dict = PyDict::new(py); for key in exp_params { let key = key?; bind_dict.set_item(&key, parameter_map.get_item(&key)?)?; @@ -769,7 +769,7 @@ fn replace_node( if let Param::ParameterExpression(old_phase) = target_dag.global_phase() { let bound_old_phase = old_phase.bind(py); - let bind_dict = PyDict::new_bound(py); + let bind_dict = PyDict::new(py); for key in target_dag.global_phase().iter_parameters(py)? { let key = key?; bind_dict.set_item(&key, parameter_map.get_item(&key)?)?; @@ -790,7 +790,7 @@ fn replace_node( } if !new_phase.getattr(intern!(py, "parameters"))?.is_truthy()? { new_phase = new_phase.call_method0(intern!(py, "numeric"))?; - if new_phase.is_instance(&PyComplex::type_object_bound(py))? { + if new_phase.is_instance(&PyComplex::type_object(py))? { return Err(TranspilerError::new_err(format!( "Global phase must be real, but got {}", new_phase.repr()? diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs index 4d4702d18b49..95e1bd0ee2b0 100644 --- a/crates/accelerate/src/check_map.rs +++ b/crates/accelerate/src/check_map.rs @@ -43,7 +43,7 @@ fn recurse<'py>( if let OperationRef::Instruction(py_inst) = inst.op.view() { let raw_blocks = py_inst.instruction.getattr(py, "blocks")?; let circuit_to_dag = CIRCUIT_TO_DAG.get_bound(py); - for raw_block in raw_blocks.bind(py).iter().unwrap() { + for raw_block in raw_blocks.bind(py).try_iter().unwrap() { let block_obj = raw_block?; let block = block_obj .getattr(intern!(py, "_data"))? diff --git a/crates/accelerate/src/circuit_library/blocks.rs b/crates/accelerate/src/circuit_library/blocks.rs index 80add611abb1..0e6083dadf57 100644 --- a/crates/accelerate/src/circuit_library/blocks.rs +++ b/crates/accelerate/src/circuit_library/blocks.rs @@ -42,8 +42,7 @@ impl BlockOperation { )), Self::PyCustom { builder } => { // the builder returns a Python operation plus the bound parameters - let py_params = - PyList::new_bound(py, params.iter().map(|&p| p.clone().into_py(py))).into_any(); + let py_params = PyList::new(py, params.iter().map(|&p| p.clone()))?.into_any(); let job = builder.call1(py, (py_params,))?; let result = job.downcast_bound::(py)?; @@ -51,7 +50,7 @@ impl BlockOperation { let operation: OperationFromPython = result.get_item(0)?.extract()?; let bound_params = result .get_item(1)? - .iter()? + .try_iter()? .map(|ob| Param::extract_no_coerce(&ob?)) .collect::>>()?; @@ -84,7 +83,6 @@ impl Block { #[staticmethod] #[pyo3(signature = (num_qubits, num_parameters, builder,))] pub fn from_callable( - py: Python, num_qubits: i64, num_parameters: i64, builder: &Bound, @@ -96,7 +94,7 @@ impl Block { } let block = Block { operation: BlockOperation::PyCustom { - builder: builder.to_object(py), + builder: builder.clone().unbind(), }, num_qubits: num_qubits as u32, num_parameters: num_parameters as usize, diff --git a/crates/accelerate/src/circuit_library/entanglement.rs b/crates/accelerate/src/circuit_library/entanglement.rs index 2168414cc4b0..0a879e5849c6 100644 --- a/crates/accelerate/src/circuit_library/entanglement.rs +++ b/crates/accelerate/src/circuit_library/entanglement.rs @@ -247,7 +247,7 @@ pub fn get_entangler_map<'py>( Ok(entanglement) => entanglement .into_iter() .map(|vec| match vec { - Ok(vec) => Ok(PyTuple::new_bound(py, vec)), + Ok(vec) => PyTuple::new(py, vec), Err(e) => Err(e), }) .collect::, _>>(), diff --git a/crates/accelerate/src/circuit_library/parameter_ledger.rs b/crates/accelerate/src/circuit_library/parameter_ledger.rs index 457034850196..feb00ac22d3f 100644 --- a/crates/accelerate/src/circuit_library/parameter_ledger.rs +++ b/crates/accelerate/src/circuit_library/parameter_ledger.rs @@ -29,7 +29,7 @@ pub(super) type LayerParameters<'a> = Vec>; // parameter in /// Internally, the parameters are stored in a 1-D vector and the ledger keeps track of /// which indices belong to which layer. For example, a 2-qubit circuit where both the /// rotation and entanglement layer have 1 block with 2 parameters each, we would store -/// +/// /// [x0 x1 x2 x3 x4 x5 x6 x7 ....] /// ----- ----- ----- ----- /// rep0 rep0 rep1 rep2 @@ -105,7 +105,7 @@ impl ParameterLedger { let parameter_vector: Vec = imports::PARAMETER_VECTOR .get_bound(py) .call1((parameter_prefix, num_parameters))? // get the Python ParameterVector - .iter()? // iterate over the elements and cast them to Rust Params + .try_iter()? // iterate over the elements and cast them to Rust Params .map(|ob| Param::extract_no_coerce(&ob?)) .collect::>()?; diff --git a/crates/accelerate/src/circuit_library/pauli_feature_map.rs b/crates/accelerate/src/circuit_library/pauli_feature_map.rs index bb9f8c25eb24..66bea22a7231 100644 --- a/crates/accelerate/src/circuit_library/pauli_feature_map.rs +++ b/crates/accelerate/src/circuit_library/pauli_feature_map.rs @@ -67,12 +67,12 @@ pub fn pauli_feature_map( let pauli_strings = _get_paulis(feature_dimension, paulis)?; // set the default value for entanglement - let default = PyString::new_bound(py, "full"); + let default = PyString::new(py, "full"); let entanglement = entanglement.unwrap_or(&default); // extract the parameters from the input variable ``parameters`` let parameter_vector = parameters - .iter()? + .try_iter()? .map(|el| Param::extract_no_coerce(&el?)) .collect::>>()?; diff --git a/crates/accelerate/src/circuit_library/quantum_volume.rs b/crates/accelerate/src/circuit_library/quantum_volume.rs index a6c5a3839d90..463c123eabb7 100644 --- a/crates/accelerate/src/circuit_library/quantum_volume.rs +++ b/crates/accelerate/src/circuit_library/quantum_volume.rs @@ -113,7 +113,7 @@ pub fn quantum_volume( let num_unitaries = width * depth; let mut permutation: Vec = (0..num_qubits).map(Qubit).collect(); - let kwargs = PyDict::new_bound(py); + let kwargs = PyDict::new(py); kwargs.set_item(intern!(py, "num_qubits"), 2)?; let mut build_instruction = |(unitary_index, unitary_array): (usize, Array2), rng: &mut Pcg64Mcg| @@ -122,7 +122,7 @@ pub fn quantum_volume( if layer_index == 0 { permutation.shuffle(rng); } - let unitary = unitary_array.into_pyarray_bound(py); + let unitary = unitary_array.into_pyarray(py); let unitary_gate = UNITARY_GATE .get_bound(py) @@ -137,7 +137,7 @@ pub fn quantum_volume( let qubit = layer_index * 2; Ok(( PackedOperation::from_gate(Box::new(instruction)), - smallvec![Param::Obj(unitary.unbind().into())], + smallvec![Param::Obj(unitary.into_any().unbind())], vec![permutation[qubit], permutation[qubit + 1]], vec![], )) diff --git a/crates/accelerate/src/commutation_analysis.rs b/crates/accelerate/src/commutation_analysis.rs index 07266191fe45..4891a9c882c8 100644 --- a/crates/accelerate/src/commutation_analysis.rs +++ b/crates/accelerate/src/commutation_analysis.rs @@ -146,39 +146,40 @@ pub(crate) fn analyze_commutations( // The Python dict will store both of these dictionaries in one. let (commutation_set, node_indices) = analyze_commutations_inner(py, dag, commutation_checker)?; - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); // First set the {wire: [commuting_nodes_1, ...]} bit for (wire, commutations) in commutation_set { // we know all wires are of type Wire::Qubit, since in analyze_commutations_inner // we only iterater over the qubits let py_wire = match wire { - Wire::Qubit(q) => dag.qubits().get(q).unwrap().to_object(py), + Wire::Qubit(q) => dag.qubits().get(q).unwrap().into_pyobject(py), _ => return Err(PyValueError::new_err("Unexpected wire type.")), - }; + }?; out_dict.set_item( py_wire, - PyList::new_bound( + PyList::new( py, commutations.iter().map(|inner| { - PyList::new_bound( + PyList::new( py, inner .iter() .map(|node_index| dag.get_node(py, *node_index).unwrap()), ) + .unwrap() }), - ), + )?, )?; } // Then we add the {(node, wire): index} dictionary for ((node_index, wire), index) in node_indices { let py_wire = match wire { - Wire::Qubit(q) => dag.qubits().get(q).unwrap().to_object(py), + Wire::Qubit(q) => dag.qubits().get(q).unwrap().into_pyobject(py), _ => return Err(PyValueError::new_err("Unexpected wire type.")), - }; + }?; out_dict.set_item((dag.get_node(py, node_index)?, py_wire), index)?; } diff --git a/crates/accelerate/src/commutation_checker.rs b/crates/accelerate/src/commutation_checker.rs index 52d4900efa5b..7e1a1903f9b4 100644 --- a/crates/accelerate/src/commutation_checker.rs +++ b/crates/accelerate/src/commutation_checker.rs @@ -21,7 +21,8 @@ use numpy::PyReadonlyArray2; use pyo3::exceptions::PyRuntimeError; use pyo3::intern; use pyo3::prelude::*; -use pyo3::types::{IntoPyDict, PyBool, PyDict, PySequence, PyTuple}; +use pyo3::types::{PyBool, PyDict, PySequence, PyTuple}; +use pyo3::BoundObject; use qiskit_circuit::bit_data::BitData; use qiskit_circuit::circuit_instruction::{ExtraInstructionAttributes, OperationFromPython}; @@ -174,14 +175,10 @@ impl CommutationChecker { cargs2: Option<&Bound>, max_num_qubits: u32, ) -> PyResult { - let qargs1 = - qargs1.map_or_else(|| Ok(PyTuple::empty_bound(py)), PySequenceMethods::to_tuple)?; - let cargs1 = - cargs1.map_or_else(|| Ok(PyTuple::empty_bound(py)), PySequenceMethods::to_tuple)?; - let qargs2 = - qargs2.map_or_else(|| Ok(PyTuple::empty_bound(py)), PySequenceMethods::to_tuple)?; - let cargs2 = - cargs2.map_or_else(|| Ok(PyTuple::empty_bound(py)), PySequenceMethods::to_tuple)?; + let qargs1 = qargs1.map_or_else(|| Ok(PyTuple::empty(py)), PySequenceMethods::to_tuple)?; + let cargs1 = cargs1.map_or_else(|| Ok(PyTuple::empty(py)), PySequenceMethods::to_tuple)?; + let qargs2 = qargs2.map_or_else(|| Ok(PyTuple::empty(py)), PySequenceMethods::to_tuple)?; + let cargs2 = cargs2.map_or_else(|| Ok(PyTuple::empty(py)), PySequenceMethods::to_tuple)?; let (qargs1, qargs2) = get_bits::(py, &qargs1, &qargs2)?; let (cargs1, cargs2) = get_bits::(py, &cargs1, &cargs2)?; @@ -213,15 +210,15 @@ impl CommutationChecker { } fn __getstate__(&self, py: Python) -> PyResult> { - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); out_dict.set_item("cache_max_entries", self.cache_max_entries)?; out_dict.set_item("current_cache_entries", self.current_cache_entries)?; - let cache_dict = PyDict::new_bound(py); + let cache_dict = PyDict::new(py); for (key, value) in &self.cache { cache_dict.set_item(key, commutation_entry_to_pydict(py, value)?)?; } out_dict.set_item("cache", cache_dict)?; - out_dict.set_item("library", self.library.library.to_object(py))?; + out_dict.set_item("library", self.library.library.clone().into_pyobject(py)?)?; out_dict.set_item("gates", self.gates.clone())?; Ok(out_dict.unbind()) } @@ -725,6 +722,28 @@ pub enum CommutationLibraryEntry { QubitMapping(HashMap; 2]>, bool>), } +impl<'py> IntoPyObject<'py> for CommutationLibraryEntry { + type Target = PyAny; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { + let py_out = match self { + CommutationLibraryEntry::Commutes(b) => b.into_pyobject(py)?.into_bound().into_any(), + CommutationLibraryEntry::QubitMapping(qm) => { + let out = PyDict::new(py); + for (k, v) in qm { + let key = PyTuple::new(py, k.iter().map(|q| q.map(|t| t.0)))?; + let value = PyBool::new(py, v); + out.set_item(key, value)?; + } + out.into_any() + } + }; + Ok(py_out) + } +} + impl<'py> FromPyObject<'py> for CommutationLibraryEntry { fn extract_bound(b: &Bound<'py, PyAny>) -> Result { if let Ok(b) = b.extract::() { @@ -742,25 +761,6 @@ impl<'py> FromPyObject<'py> for CommutationLibraryEntry { } } -impl ToPyObject for CommutationLibraryEntry { - fn to_object(&self, py: Python) -> PyObject { - match self { - CommutationLibraryEntry::Commutes(b) => b.into_py(py), - CommutationLibraryEntry::QubitMapping(qm) => qm - .iter() - .map(|(k, v)| { - ( - PyTuple::new_bound(py, k.iter().map(|q| q.map(|t| t.0))), - PyBool::new_bound(py, *v), - ) - }) - .into_py_dict_bound(py) - .unbind() - .into(), - } - } -} - type CacheKey = ( SmallVec<[Option; 2]>, (SmallVec<[ParameterKey; 3]>, SmallVec<[ParameterKey; 3]>), @@ -769,14 +769,14 @@ type CacheKey = ( type CommutationCacheEntry = HashMap; fn commutation_entry_to_pydict(py: Python, entry: &CommutationCacheEntry) -> PyResult> { - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (k, v) in entry.iter() { - let qubits = PyTuple::new_bound(py, k.0.iter().map(|q| q.map(|t| t.0))); - let params0 = PyTuple::new_bound(py, k.1 .0.iter().map(|pk| pk.0)); - let params1 = PyTuple::new_bound(py, k.1 .1.iter().map(|pk| pk.0)); + let qubits = PyTuple::new(py, k.0.iter().map(|q| q.map(|t| t.0)))?; + let params0 = PyTuple::new(py, k.1 .0.iter().map(|pk| pk.0))?; + let params1 = PyTuple::new(py, k.1 .1.iter().map(|pk| pk.0))?; out_dict.set_item( - PyTuple::new_bound(py, [qubits, PyTuple::new_bound(py, [params0, params1])]), - PyBool::new_bound(py, *v), + PyTuple::new(py, [qubits, PyTuple::new(py, [params0, params1])?])?, + PyBool::new(py, *v), )?; } Ok(out_dict.unbind()) diff --git a/crates/accelerate/src/consolidate_blocks.rs b/crates/accelerate/src/consolidate_blocks.rs index 0fec3fa2909a..c8e0a1f5d45a 100644 --- a/crates/accelerate/src/consolidate_blocks.rs +++ b/crates/accelerate/src/consolidate_blocks.rs @@ -112,7 +112,7 @@ pub(crate) fn consolidate_blocks( Ok(mat) => mat, Err(_) => continue, }; - let array = matrix.into_pyarray_bound(py); + let array = matrix.into_pyarray(py); let unitary_gate = UNITARY_GATE .get_bound(py) .call1((array, py.None(), false))?; @@ -180,10 +180,11 @@ pub(crate) fn consolidate_blocks( dag.remove_op_node(node); } } else { - let unitary_gate = - UNITARY_GATE - .get_bound(py) - .call1((array.to_object(py), py.None(), false))?; + let unitary_gate = UNITARY_GATE.get_bound(py).call1(( + array.as_ref().into_pyobject(py)?, + py.None(), + false, + ))?; let clbit_pos_map = HashMap::new(); dag.replace_block_with_py_op( py, @@ -212,7 +213,7 @@ pub(crate) fn consolidate_blocks( dag.remove_op_node(node); } } else { - let array = matrix.into_pyarray_bound(py); + let array = matrix.into_pyarray(py); let unitary_gate = UNITARY_GATE .get_bound(py) @@ -257,7 +258,7 @@ pub(crate) fn consolidate_blocks( Ok(mat) => mat, Err(_) => continue, }; - let array = matrix.into_pyarray_bound(py); + let array = matrix.into_pyarray(py); let unitary_gate = UNITARY_GATE .get_bound(py) .call1((array, py.None(), false))?; @@ -292,7 +293,7 @@ pub(crate) fn consolidate_blocks( dag.remove_op_node(node); } } else { - let array = aview2(&matrix).to_owned().into_pyarray_bound(py); + let array = aview2(&matrix).to_owned().into_pyarray(py); let unitary_gate = UNITARY_GATE .get_bound(py) .call1((array, py.None(), false))?; diff --git a/crates/accelerate/src/dense_layout.rs b/crates/accelerate/src/dense_layout.rs index cbe8b9ff8cc7..398a2d2cd70c 100644 --- a/crates/accelerate/src/dense_layout.rs +++ b/crates/accelerate/src/dense_layout.rs @@ -122,9 +122,9 @@ pub fn best_subset( err, ); ( - rows.into_pyarray_bound(py).into(), - cols.into_pyarray_bound(py).into(), - best_map.into_pyarray_bound(py).into(), + rows.into_pyarray(py).into_any().unbind(), + cols.into_pyarray(py).into_any().unbind(), + best_map.into_pyarray(py).into_any().unbind(), ) } diff --git a/crates/accelerate/src/edge_collections.rs b/crates/accelerate/src/edge_collections.rs index b64cbc42c356..825016229c6c 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_bound(py).into() + self.edges.clone().into_pyarray(py).into_any().unbind() } fn __getstate__(&self) -> Vec { diff --git a/crates/accelerate/src/equivalence.rs b/crates/accelerate/src/equivalence.rs index dfa338fba45a..4e763beb9e34 100644 --- a/crates/accelerate/src/equivalence.rs +++ b/crates/accelerate/src/equivalence.rs @@ -78,10 +78,7 @@ impl Key { } fn __getnewargs__(slf: PyRef) -> (Bound, u32) { - ( - PyString::new_bound(slf.py(), slf.name.as_str()), - slf.num_qubits, - ) + (PyString::new(slf.py(), slf.name.as_str()), slf.num_qubits) } // Ord methods for Python @@ -307,6 +304,19 @@ impl<'py> FromPyObject<'py> for GateOper { #[derive(Debug, Clone)] pub struct CircuitFromPython(pub CircuitData); +impl<'py> IntoPyObject<'py> for CircuitFromPython { + type Target = PyAny; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(QUANTUM_CIRCUIT + .get_bound(py) + .call_method1("_from_circuit_data", (self.0,))? + .clone()) + } +} + impl FromPyObject<'_> for CircuitFromPython { fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { if ob.is_instance(QUANTUM_CIRCUIT.get_bound(ob.py()))? { @@ -322,22 +332,6 @@ impl FromPyObject<'_> for CircuitFromPython { } } -impl IntoPy for CircuitFromPython { - fn into_py(self, py: Python<'_>) -> PyObject { - QUANTUM_CIRCUIT - .get_bound(py) - .call_method1("_from_circuit_data", (self.0,)) - .unwrap() - .unbind() - } -} - -impl ToPyObject for CircuitFromPython { - fn to_object(&self, py: Python<'_>) -> PyObject { - self.clone().into_py(py) - } -} - // Custom Types type GraphType = StableDiGraph>; type KTIType = IndexMap; @@ -465,7 +459,7 @@ impl EquivalenceLibrary { ._get_equivalences(&key) .into_iter() .filter_map(|equivalence| rebind_equiv(py, equivalence, &query_params).ok()); - let return_list = PyList::empty_bound(py); + let return_list = PyList::empty(py); for equiv in bound_equivalencies { return_list.append(equiv)?; } @@ -512,9 +506,9 @@ impl EquivalenceLibrary { /// List: Keys to the key to node index map. #[pyo3(name = "keys")] fn py_keys(slf: PyRef) -> PyResult { - let py_dict = PyDict::new_bound(slf.py()); + let py_dict = PyDict::new(slf.py()); for key in slf.keys() { - py_dict.set_item(key.clone().into_py(slf.py()), slf.py().None())?; + py_dict.set_item(key.clone(), slf.py().None())?; } Ok(py_dict.as_any().call_method0("keys")?.into()) } @@ -532,26 +526,26 @@ impl EquivalenceLibrary { } fn __getstate__(slf: PyRef) -> PyResult> { - let ret = PyDict::new_bound(slf.py()); + let ret = PyDict::new(slf.py()); ret.set_item("rule_id", slf.rule_id)?; - let key_to_usize_node: Bound = PyDict::new_bound(slf.py()); + let key_to_usize_node: Bound = PyDict::new(slf.py()); for (key, val) in slf.key_to_node_index.iter() { - key_to_usize_node.set_item(key.clone().into_py(slf.py()), val.index())?; + key_to_usize_node.set_item(key.clone(), val.index())?; } ret.set_item("key_to_node_index", key_to_usize_node)?; - let graph_nodes: Bound = PyList::empty_bound(slf.py()); + let graph_nodes: Bound = PyList::empty(slf.py()); for weight in slf.graph.node_weights() { - graph_nodes.append(weight.clone().into_py(slf.py()))?; + graph_nodes.append(weight.clone())?; } ret.set_item("graph_nodes", graph_nodes.unbind())?; let edges = slf.graph.edge_references().map(|edge| { ( edge.source().index(), edge.target().index(), - edge.weight().clone().into_py(slf.py()), + edge.weight().clone().into_pyobject(slf.py()).unwrap(), ) }); - let graph_edges = PyList::empty_bound(slf.py()); + let graph_edges = PyList::empty(slf.py()); for edge in edges { graph_edges.append(edge)?; } @@ -720,7 +714,7 @@ fn raise_if_param_mismatch( gate_params: &[Param], circuit_parameters: Bound, ) -> PyResult<()> { - let gate_params_obj = PySet::new_bound( + let gate_params_obj = PySet::new( py, gate_params .iter() @@ -801,10 +795,10 @@ impl Display for EquivalenceError { // Conversion helpers -fn to_pygraph(py: Python, pet_graph: &StableDiGraph) -> PyResult +fn to_pygraph<'py, N, E>(py: Python<'py>, pet_graph: &'py StableDiGraph) -> PyResult where - N: IntoPy + Clone, - E: IntoPy + Clone, + N: IntoPyObject<'py> + Clone, + E: IntoPyObject<'py> + Clone, { let graph = PYDIGRAPH.get_bound(py).call0()?; let node_weights: Vec = pet_graph.node_weights().cloned().collect(); diff --git a/crates/accelerate/src/error_map.rs b/crates/accelerate/src/error_map.rs index 1fe3cb254914..398d504d7840 100644 --- a/crates/accelerate/src/error_map.rs +++ b/crates/accelerate/src/error_map.rs @@ -12,6 +12,7 @@ use pyo3::exceptions::PyIndexError; use pyo3::prelude::*; +use pyo3::IntoPyObjectExt; use crate::nlayout::PhysicalQubit; @@ -101,14 +102,19 @@ impl ErrorMap { } #[pyo3(signature=(key, default=None))] - fn get(&self, py: Python, key: [PhysicalQubit; 2], default: Option) -> PyObject { - match self.error_map.get(&key).copied() { - Some(val) => val.to_object(py), + fn get( + &self, + py: Python, + key: [PhysicalQubit; 2], + default: Option, + ) -> PyResult { + Ok(match self.error_map.get(&key).copied() { + Some(val) => val.into_py_any(py)?, None => match default { Some(val) => val, None => py.None(), }, - } + }) } } diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index eb53b8309b05..d29e2cbcddac 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -24,6 +24,7 @@ use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::types::{PyList, PyString}; use pyo3::wrap_pyfunction; +use pyo3::IntoPyObjectExt; use pyo3::Python; use ndarray::prelude::*; @@ -109,11 +110,13 @@ impl OneQubitGateSequence { fn __getitem__(&self, py: Python, idx: PySequenceIndex) -> PyResult { match idx.with_len(self.gates.len())? { - SequenceIndex::Int(idx) => Ok(self.gates[idx].to_object(py)), - indices => Ok(PyList::new_bound( + SequenceIndex::Int(idx) => Ok(self.gates[idx].clone().into_py_any(py)?), + indices => Ok(PyList::new( py, - indices.iter().map(|pos| self.gates[pos].to_object(py)), - ) + indices + .iter() + .map(|pos| self.gates[pos].clone().into_pyobject(py).unwrap()), + )? .into_any() .unbind()), } @@ -712,12 +715,8 @@ impl EulerBasis { #[pymethods] impl EulerBasis { - fn __reduce__(&self, py: Python) -> Py { - ( - py.get_type_bound::(), - (PyString::new_bound(py, self.as_str()),), - ) - .into_py(py) + fn __reduce__(&self, py: Python) -> PyResult> { + (py.get_type::(), (PyString::new(py, self.as_str()),)).into_py_any(py) } #[new] diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index a20dfea00535..9ca0ed76633f 100755 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -17,6 +17,7 @@ use hashbrown::HashSet; use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyTuple; +use pyo3::IntoPyObjectExt; use qiskit_circuit::operations::OperationRef; use qiskit_circuit::packed_instruction::PackedOperation; use qiskit_circuit::{ @@ -117,7 +118,7 @@ where let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py); let py_inst = py_inst.instruction.bind(py); - for block in py_inst.getattr("blocks")?.iter()? { + for block in py_inst.getattr("blocks")?.try_iter()? { let inner_dag: DAGCircuit = circuit_to_dag.call1((block?,))?.extract()?; let block_ok = if let Some(mapping) = qubit_mapping { @@ -389,7 +390,7 @@ fn has_calibration_for_op_node( packed_inst: &PackedInstruction, qargs: &[Qubit], ) -> PyResult { - let py_args = PyTuple::new_bound(py, dag.qubits().map_indices(qargs)); + let py_args = PyTuple::new(py, dag.qubits().map_indices(qargs))?; let dag_op_node = Py::new( py, @@ -398,13 +399,13 @@ fn has_calibration_for_op_node( instruction: CircuitInstruction { operation: packed_inst.op.clone(), qubits: py_args.unbind(), - clbits: PyTuple::empty_bound(py).unbind(), + clbits: PyTuple::empty(py).unbind(), params: packed_inst.params_view().iter().cloned().collect(), extra_attrs: packed_inst.extra_attrs.clone(), #[cfg(feature = "cache_pygates")] py_op: packed_inst.py_op.clone(), }, - sort_key: "".into_py(py), + sort_key: "".into_py_any(py)?, }, DAGNode { node: None }, ), diff --git a/crates/accelerate/src/isometry.rs b/crates/accelerate/src/isometry.rs index a54116b2b2f9..38e49084d840 100644 --- a/crates/accelerate/src/isometry.rs +++ b/crates/accelerate/src/isometry.rs @@ -36,8 +36,9 @@ pub fn reverse_qubit_state( epsilon: f64, ) -> PyObject { reverse_qubit_state_inner(&state, basis_state, epsilon) - .into_pyarray_bound(py) - .into() + .into_pyarray(py) + .into_any() + .unbind() } #[inline(always)] @@ -105,7 +106,7 @@ pub fn find_squs_for_disentangling( output.append(&mut squs); output .into_iter() - .map(|x| x.into_pyarray_bound(py).into()) + .map(|x| x.into_pyarray(py).into_any().unbind()) .collect() } @@ -132,7 +133,7 @@ pub fn apply_ucg( m[[i + spacing, col]] = gate[[1, 0]] * a + gate[[1, 1]] * b; } } - m.into_pyarray_bound(py).into() + m.into_pyarray(py).into_any().unbind() } #[inline(always)] @@ -165,7 +166,7 @@ pub fn apply_diagonal_gate( m[[i, j]] = diag[diag_index] * m[[i, j]] } } - Ok(m.into_pyarray_bound(py).into()) + Ok(m.into_pyarray(py).into_any().unbind()) } #[pyfunction] @@ -247,7 +248,7 @@ pub fn apply_multi_controlled_gate( m[[e1, i]] = temp[0]; m[[e2, i]] = temp[1]; } - return m.into_pyarray_bound(py).into(); + return m.into_pyarray(py).into_any().unbind(); } for state_free in std::iter::repeat([0_u8, 1_u8]) .take(free_qubits) @@ -264,7 +265,7 @@ pub fn apply_multi_controlled_gate( m[[e2, i]] = temp[1]; } } - m.into_pyarray_bound(py).into() + m.into_pyarray(py).into_any().unbind() } #[pyfunction] @@ -317,7 +318,7 @@ pub fn merge_ucgate_and_diag( .map(|(i, raw_gate)| { let gate = raw_gate.as_array(); let res = aview2(&[[diag[2 * i], C_ZERO], [C_ZERO, diag[2 * i + 1]]]).dot(&gate); - res.into_pyarray_bound(py).into() + res.into_pyarray(py).into_any().unbind() }) .collect() } diff --git a/crates/accelerate/src/nlayout.rs b/crates/accelerate/src/nlayout.rs index dcd6e71fafa8..82f461fa0d61 100644 --- a/crates/accelerate/src/nlayout.rs +++ b/crates/accelerate/src/nlayout.rs @@ -12,6 +12,7 @@ use pyo3::prelude::*; use pyo3::types::PyList; +use pyo3::IntoPyObjectExt; use hashbrown::HashMap; @@ -23,7 +24,9 @@ use hashbrown::HashMap; /// overhead, so we just allow conversion to and from any valid `PyLong`. macro_rules! qubit_newtype { ($id: ident) => { - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, IntoPyObject, IntoPyObjectRef, + )] pub struct $id(pub u32); impl $id { @@ -37,17 +40,6 @@ macro_rules! qubit_newtype { } } - impl pyo3::IntoPy for $id { - fn into_py(self, py: Python<'_>) -> PyObject { - self.0.into_py(py) - } - } - impl pyo3::ToPyObject for $id { - fn to_object(&self, py: Python<'_>) -> PyObject { - self.0.to_object(py) - } - } - impl pyo3::FromPyObject<'_> for $id { fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { Ok(Self(ob.extract()?)) @@ -57,8 +49,8 @@ macro_rules! qubit_newtype { unsafe impl numpy::Element for $id { const IS_COPY: bool = true; - fn get_dtype_bound(py: Python<'_>) -> Bound<'_, numpy::PyArrayDescr> { - u32::get_dtype_bound(py) + fn get_dtype(py: Python<'_>) -> Bound<'_, numpy::PyArrayDescr> { + u32::get_dtype(py) } fn clone_ref(&self, _py: Python<'_>) -> Self { @@ -123,12 +115,11 @@ impl NLayout { } fn __reduce__(&self, py: Python) -> PyResult> { - Ok(( - py.get_type_bound::() - .getattr("from_virtual_to_physical")?, - (self.virt_to_phys.to_object(py),), + ( + py.get_type::().getattr("from_virtual_to_physical")?, + (self.virt_to_phys.clone().into_pyobject(py)?,), ) - .into_py(py)) + .into_py_any(py) } /// Return the layout mapping. @@ -143,8 +134,8 @@ impl NLayout { /// where the virtual qubit is the index in the qubit index in the circuit. /// #[pyo3(text_signature = "(self, /)")] - fn layout_mapping(&self, py: Python<'_>) -> Py { - PyList::new_bound(py, self.iter_virtual()).into() + fn layout_mapping(&self, py: Python<'_>) -> PyResult> { + PyList::new(py, self.iter_virtual()).map(|x| x.unbind()) } /// Get physical bit from virtual bit diff --git a/crates/accelerate/src/results/marginalization.rs b/crates/accelerate/src/results/marginalization.rs index 83fb5097ac36..85c2e77fd9d4 100644 --- a/crates/accelerate/src/results/marginalization.rs +++ b/crates/accelerate/src/results/marginalization.rs @@ -19,6 +19,7 @@ use num_complex::Complex64; use numpy::IntoPyArray; use numpy::{PyReadonlyArray1, PyReadonlyArray2, PyReadonlyArray3}; use pyo3::prelude::*; +use pyo3::IntoPyObjectExt; use rayon::prelude::*; fn marginalize( @@ -132,7 +133,7 @@ pub fn marginal_memory( let first_elem = memory.first(); if first_elem.is_none() { let res: Vec = Vec::new(); - return Ok(res.to_object(py)); + return res.into_py_any(py); } let clbit_size = hex_to_bin(first_elem.unwrap()).len(); @@ -154,16 +155,16 @@ pub fn marginal_memory( .iter() .map(|x| BigUint::parse_bytes(x.as_bytes(), 2).unwrap()) .collect::>() - .to_object(py)) + .into_py_any(py)?) } else { Ok(out_mem .par_iter() .map(|x| BigUint::parse_bytes(x.as_bytes(), 2).unwrap()) .collect::>() - .to_object(py)) + .into_py_any(py)?) } } else { - Ok(out_mem.to_object(py)) + out_mem.into_py_any(py) } } @@ -178,7 +179,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_bound(py).into() + out_arr.into_pyarray(py).into_any().unbind() } #[pyfunction] @@ -192,7 +193,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_bound(py).into() + out_arr.into_pyarray(py).into_any().unbind() } #[pyfunction] @@ -206,7 +207,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_bound(py).into() + out_arr.into_pyarray(py).into_any().unbind() } #[pyfunction] @@ -217,5 +218,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_bound(py).into()) + Ok(out_arr.into_pyarray(py).into_any().unbind()) } diff --git a/crates/accelerate/src/sabre/heuristic.rs b/crates/accelerate/src/sabre/heuristic.rs index ea3b73265c77..6c5469ee9fb8 100644 --- a/crates/accelerate/src/sabre/heuristic.rs +++ b/crates/accelerate/src/sabre/heuristic.rs @@ -13,6 +13,7 @@ use pyo3::exceptions::PyValueError; use pyo3::prelude::*; use pyo3::types::PyString; +use pyo3::IntoPyObjectExt; use pyo3::Python; /// Affect the dynamic scaling of the weight of node-set-based heuristics (basic and lookahead). @@ -33,11 +34,11 @@ impl SetScaling { SetScaling::Constant => "Constant", SetScaling::Size => "Size", }; - Ok(( - py.import_bound("builtins")?.getattr("getattr")?, - (py.get_type_bound::(), name), + ( + py.import("builtins")?.getattr("getattr")?, + (py.get_type::(), name), ) - .into_py(py)) + .into_py_any(py) } } @@ -60,8 +61,8 @@ impl BasicHeuristic { Self { weight, scale } } - pub fn __getnewargs__(&self, py: Python) -> Py { - (self.weight, self.scale).into_py(py) + pub fn __getnewargs__(&self, py: Python) -> PyResult> { + (self.weight, self.scale).into_py_any(py) } pub fn __eq__(&self, py: Python, other: Py) -> bool { @@ -74,9 +75,9 @@ impl BasicHeuristic { pub fn __repr__(&self, py: Python) -> PyResult> { let fmt = "BasicHeuristic(weight={!r}, scale={!r})"; - Ok(PyString::new_bound(py, fmt) + PyString::new(py, fmt) .call_method1("format", (self.weight, self.scale))? - .into_py(py)) + .into_py_any(py) } } @@ -105,8 +106,8 @@ impl LookaheadHeuristic { } } - pub fn __getnewargs__(&self, py: Python) -> Py { - (self.weight, self.size, self.scale).into_py(py) + pub fn __getnewargs__(&self, py: Python) -> PyResult> { + (self.weight, self.size, self.scale).into_py_any(py) } pub fn __eq__(&self, py: Python, other: Py) -> bool { @@ -119,9 +120,9 @@ impl LookaheadHeuristic { pub fn __repr__(&self, py: Python) -> PyResult> { let fmt = "LookaheadHeuristic(weight={!r}, size={!r}, scale={!r})"; - Ok(PyString::new_bound(py, fmt) + PyString::new(py, fmt) .call_method1("format", (self.weight, self.size, self.scale))? - .into_py(py)) + .into_py_any(py) } } @@ -145,8 +146,8 @@ impl DecayHeuristic { Self { increment, reset } } - pub fn __getnewargs__(&self, py: Python) -> Py { - (self.increment, self.reset).into_py(py) + pub fn __getnewargs__(&self, py: Python) -> PyResult> { + (self.increment, self.reset).into_py_any(py) } pub fn __eq__(&self, py: Python, other: Py) -> bool { @@ -159,9 +160,9 @@ impl DecayHeuristic { pub fn __repr__(&self, py: Python) -> PyResult> { let fmt = "DecayHeuristic(increment={!r}, reset={!r})"; - Ok(PyString::new_bound(py, fmt) + PyString::new(py, fmt) .call_method1("format", (self.increment, self.reset))? - .into_py(py)) + .into_py_any(py) } } @@ -211,7 +212,7 @@ impl Heuristic { } } - pub fn __getnewargs__(&self, py: Python) -> Py { + pub fn __getnewargs__(&self, py: Python) -> PyResult> { ( self.basic, self.lookahead, @@ -219,7 +220,7 @@ impl Heuristic { self.attempt_limit, self.best_epsilon, ) - .into_py(py) + .into_py_any(py) } /// Set the weight of the ``basic`` heuristic (the sum of distances of gates in the front @@ -268,7 +269,7 @@ impl Heuristic { pub fn __repr__(&self, py: Python) -> PyResult> { let fmt = "Heuristic(basic={!r}, lookahead={!r}, decay={!r}, attempt_limit={!r}, best_epsilon={!r})"; - Ok(PyString::new_bound(py, fmt) + PyString::new(py, fmt) .call_method1( "format", ( @@ -279,6 +280,6 @@ impl Heuristic { self.best_epsilon, ), )? - .into_py(py)) + .into_py_any(py) } } diff --git a/crates/accelerate/src/sabre/layout.rs b/crates/accelerate/src/sabre/layout.rs index 9ab67c3fcfbc..cc4ab7ff19f7 100644 --- a/crates/accelerate/src/sabre/layout.rs +++ b/crates/accelerate/src/sabre/layout.rs @@ -182,10 +182,10 @@ pub fn sabre_layout_and_routing( }; ( res.0, - PyArray::from_vec_bound(py, res.1).into(), + PyArray::from_vec(py, res.1).into_any().unbind(), ( res.2.map, - res.2.node_order.into_pyarray_bound(py).into(), + res.2.node_order.into_pyarray(py).into_any().unbind(), res.2.node_block_results, ), ) diff --git a/crates/accelerate/src/sabre/mod.rs b/crates/accelerate/src/sabre/mod.rs index 77057b69c272..9e386ccb431b 100644 --- a/crates/accelerate/src/sabre/mod.rs +++ b/crates/accelerate/src/sabre/mod.rs @@ -23,6 +23,7 @@ use numpy::{IntoPyArray, ToPyArray}; use pyo3::exceptions::PyIndexError; use pyo3::prelude::*; use pyo3::wrap_pyfunction; +use pyo3::IntoPyObjectExt; use pyo3::Python; use crate::nlayout::PhysicalQubit; @@ -45,7 +46,7 @@ pub struct SabreResult { impl SabreResult { #[getter] fn node_order(&self, py: Python) -> PyObject { - self.node_order.to_pyarray_bound(py).into() + self.node_order.to_pyarray(py).into_any().unbind() } } @@ -70,10 +71,11 @@ impl NodeBlockResults { match self.results.get(&object) { Some(val) => Ok(val .iter() - .map(|x| x.clone().into_py(py)) - .collect::>() - .into_pyarray_bound(py) - .into()), + .map(|x| x.clone().into_py_any(py)) + .collect::>>()? + .into_pyarray(py) + .into_any() + .unbind()), None => Err(PyIndexError::new_err(format!( "Node index {object} has no block results", ))), @@ -96,13 +98,15 @@ pub struct BlockResult { #[pymethods] impl BlockResult { #[getter] - fn swap_epilogue(&self, py: Python) -> PyObject { - self.swap_epilogue + fn swap_epilogue(&self, py: Python) -> PyResult { + Ok(self + .swap_epilogue .iter() - .map(|x| x.into_py(py)) - .collect::>() - .into_pyarray_bound(py) - .into() + .map(|x| x.into_py_any(py)) + .collect::>>()? + .into_pyarray(py) + .into_any() + .unbind()) } } diff --git a/crates/accelerate/src/sabre/neighbor_table.rs b/crates/accelerate/src/sabre/neighbor_table.rs index 8ab80dd81a2a..e9af0945cdc7 100644 --- a/crates/accelerate/src/sabre/neighbor_table.rs +++ b/crates/accelerate/src/sabre/neighbor_table.rs @@ -107,14 +107,17 @@ impl NeighborTable { Ok(NeighborTable { neighbors }) } - fn __getstate__(&self, py: Python<'_>) -> Py { - PyList::new_bound( + fn __getstate__(&self, py: Python<'_>) -> PyResult> { + PyList::new( py, - self.neighbors - .iter() - .map(|v| PyList::new_bound(py, v.iter()).to_object(py)), + self.neighbors.iter().map(|v| { + PyList::new(py, v.iter()) + .unwrap() + .into_pyobject(py) + .unwrap() + }), ) - .into() + .map(|x| x.unbind()) } fn __setstate__(&mut self, state: &Bound) -> PyResult<()> { diff --git a/crates/accelerate/src/sabre/route.rs b/crates/accelerate/src/sabre/route.rs index 82d83d607a6e..a1ec511f1aad 100644 --- a/crates/accelerate/src/sabre/route.rs +++ b/crates/accelerate/src/sabre/route.rs @@ -470,9 +470,9 @@ pub fn sabre_routing( ); ( res.map, - res.node_order.into_pyarray_bound(py).into(), + res.node_order.into_pyarray(py).into_any().unbind(), res.node_block_results, - PyArray::from_iter_bound( + PyArray::from_iter( py, (0u32..neighbor_table.num_qubits().try_into().unwrap()).map(|phys| { PhysicalQubit::new(phys) @@ -480,7 +480,8 @@ pub fn sabre_routing( .to_phys(&final_layout) }), ) - .into(), + .into_any() + .unbind(), ) } diff --git a/crates/accelerate/src/sparse_observable.rs b/crates/accelerate/src/sparse_observable.rs index e1d2f2689d28..ff117be76885 100644 --- a/crates/accelerate/src/sparse_observable.rs +++ b/crates/accelerate/src/sparse_observable.rs @@ -27,6 +27,8 @@ use pyo3::intern; use pyo3::prelude::*; use pyo3::sync::GILOnceCell; use pyo3::types::{IntoPyDict, PyList, PyType}; +use pyo3::BoundObject; +use pyo3::IntoPyObjectExt; use qiskit_circuit::imports::{ImportOnceCell, NUMPY_COPY_ONLY_IF_NEEDED}; use qiskit_circuit::slice::{PySequenceIndex, SequenceIndex}; @@ -213,14 +215,14 @@ fn make_py_bit_term(py: Python) -> PyResult> { out }) .collect::>(); - let obj = py.import_bound("enum")?.getattr("IntEnum")?.call( + let obj = py.import("enum")?.getattr("IntEnum")?.call( ("BitTerm", terms), Some( &[ ("module", "qiskit.quantum_info"), ("qualname", "SparseObservable.BitTerm"), ] - .into_py_dict_bound(py), + .into_py_dict(py)?, ), )?; Ok(obj.downcast_into::()?.unbind()) @@ -230,8 +232,12 @@ fn make_py_bit_term(py: Python) -> PyResult> { // singletons and subclasses of Python `int`. We only use this for interaction with "high level" // Python space; the efficient Numpy-like array paths use `u8` directly so Numpy can act on it // efficiently. -impl IntoPy> for BitTerm { - fn into_py(self, py: Python) -> Py { +impl<'py> IntoPyObject<'py> for BitTerm { + type Target = PyAny; // the Python type + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { let terms = BIT_TERM_INTO_PY.get_or_init(py, || { let py_enum = BIT_TERM_PY_ENUM .get_or_try_init(py, || make_py_bit_term(py)) @@ -248,17 +254,15 @@ impl IntoPy> for BitTerm { }) }) }); - terms[self as usize] + Ok(terms[self as usize] .as_ref() .expect("the lookup table initializer populated a 'Some' in all valid locations") .clone_ref(py) + .bind(py) + .to_owned()) } } -impl ToPyObject for BitTerm { - fn to_object(&self, py: Python) -> Py { - self.into_py(py) - } -} + impl<'py> FromPyObject<'py> for BitTerm { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let value = ob @@ -1244,7 +1248,7 @@ impl SparseObservable { #[allow(non_snake_case)] #[classattr] fn Term(py: Python) -> Bound { - py.get_type_bound::() + py.get_type::() } /// Get the zero operator over the given number of qubits. @@ -1315,14 +1319,14 @@ impl SparseObservable { fn __getitem__(&self, py: Python, index: PySequenceIndex) -> PyResult> { let indices = match index.with_len(self.num_terms())? { - SequenceIndex::Int(index) => return Ok(self.term(index).to_term().into_py(py)), + SequenceIndex::Int(index) => return self.term(index).to_term().into_py_any(py), indices => indices, }; let mut out = SparseObservable::zero(self.num_qubits); for index in indices.iter() { out.add_term(self.term(index))?; } - Ok(out.into_py(py)) + Ok(out.into_pyobject(py)?.into_any().unbind()) } fn __repr__(&self) -> String { @@ -1353,17 +1357,19 @@ impl SparseObservable { fn __reduce__(&self, py: Python) -> PyResult> { let bit_terms: &[u8] = ::bytemuck::cast_slice(&self.bit_terms); Ok(( - py.get_type_bound::().getattr("from_raw_parts")?, + py.get_type::().getattr("from_raw_parts")?, ( self.num_qubits, - PyArray1::from_slice_bound(py, &self.coeffs), - PyArray1::from_slice_bound(py, bit_terms), - PyArray1::from_slice_bound(py, &self.indices), - PyArray1::from_slice_bound(py, &self.boundaries), + PyArray1::from_slice(py, &self.coeffs), + PyArray1::from_slice(py, bit_terms), + PyArray1::from_slice(py, &self.indices), + PyArray1::from_slice(py, &self.boundaries), false, ), ) - .into_py(py)) + .into_pyobject(py)? + .into_any() + .unbind()) } fn __eq__(slf: Bound, other: Bound) -> bool { @@ -1385,7 +1391,9 @@ impl SparseObservable { &slf_.borrow(), Complex64::new(2.0, 0.0), ) - .into_py(py)); + .into_pyobject(py)? + .into_any() + .unbind()); } let Some(other) = coerce_to_observable(other)? else { return Ok(py.NotImplemented()); @@ -1393,7 +1401,10 @@ impl SparseObservable { let slf_ = slf_.borrow(); let other = other.borrow(); slf_.check_equal_qubits(&other)?; - Ok(<&SparseObservable as ::std::ops::Add>::add(&slf_, &other).into_py(py)) + Ok(<&SparseObservable as ::std::ops::Add>::add(&slf_, &other) + .into_pyobject(py)? + .into_any() + .unbind()) } fn __radd__(&self, other: &Bound) -> PyResult> { // No need to handle the `self is other` case here, because `__add__` will get it. @@ -1403,7 +1414,10 @@ impl SparseObservable { }; let other = other.borrow(); self.check_equal_qubits(&other)?; - Ok((<&SparseObservable as ::std::ops::Add>::add(&other, self)).into_py(py)) + Ok((<&SparseObservable as ::std::ops::Add>::add(&other, self)) + .into_pyobject(py)? + .into_any() + .unbind()) } fn __iadd__(slf_: Bound, other: &Bound) -> PyResult<()> { if slf_.is(other) { @@ -1429,7 +1443,10 @@ impl SparseObservable { fn __sub__(slf_: &Bound, other: &Bound) -> PyResult> { let py = slf_.py(); if slf_.is(other) { - return Ok(SparseObservable::zero(slf_.borrow().num_qubits).into_py(py)); + return Ok(SparseObservable::zero(slf_.borrow().num_qubits) + .into_pyobject(py)? + .into_any() + .unbind()); } let Some(other) = coerce_to_observable(other)? else { return Ok(py.NotImplemented()); @@ -1437,7 +1454,10 @@ impl SparseObservable { let slf_ = slf_.borrow(); let other = other.borrow(); slf_.check_equal_qubits(&other)?; - Ok(<&SparseObservable as ::std::ops::Sub>::sub(&slf_, &other).into_py(py)) + Ok(<&SparseObservable as ::std::ops::Sub>::sub(&slf_, &other) + .into_pyobject(py)? + .into_any() + .unbind()) } fn __rsub__(&self, other: &Bound) -> PyResult> { let py = other.py(); @@ -1446,7 +1466,10 @@ impl SparseObservable { }; let other = other.borrow(); self.check_equal_qubits(&other)?; - Ok((<&SparseObservable as ::std::ops::Sub>::sub(&other, self)).into_py(py)) + Ok((<&SparseObservable as ::std::ops::Sub>::sub(&other, self)) + .into_pyobject(py)? + .into_any() + .unbind()) } fn __isub__(slf_: Bound, other: &Bound) -> PyResult<()> { if slf_.is(other) { @@ -1508,14 +1531,23 @@ impl SparseObservable { let Some(other) = coerce_to_observable(other)? else { return Ok(py.NotImplemented()); }; - Ok(self.tensor(&other.borrow()).into_py(py)) + Ok(self + .tensor(&other.borrow()) + .into_pyobject(py)? + .into_any() + .unbind()) } fn __rxor__(&self, other: &Bound) -> PyResult> { let py = other.py(); let Some(other) = coerce_to_observable(other)? else { return Ok(py.NotImplemented()); }; - Ok(other.borrow().tensor(self).into_py(py)) + Ok(other + .borrow() + .tensor(self) + .into_pyobject(py)? + .into_any() + .unbind()) } // This doesn't actually have any interaction with Python space, but uses the `py_` prefix on @@ -1547,7 +1579,7 @@ impl SparseObservable { /// Examples: /// /// .. code-block:: python - /// + /// /// >>> SparseObservable.from_label("IIII+ZI") /// /// >>> label = "IYXZI" @@ -1749,7 +1781,7 @@ impl SparseObservable { /// Examples: /// /// .. code-block:: python - /// + /// /// >>> label = "IYXZI" /// >>> pauli = Pauli(label) /// >>> SparseObservable.from_pauli(pauli) @@ -1909,7 +1941,7 @@ impl SparseObservable { #[staticmethod] #[pyo3(signature = (obj, /, num_qubits=None), name="from_terms")] fn py_from_terms(obj: &Bound, num_qubits: Option) -> PyResult { - let mut iter = obj.iter()?; + let mut iter = obj.try_iter()?; let mut obs = match num_qubits { Some(num_qubits) => SparseObservable::zero(num_qubits), None => { @@ -2102,7 +2134,11 @@ impl SparseObservable { other.get_type().repr()? ))); }; - Ok(self.tensor(&other.borrow()).into_py(py)) + Ok(self + .tensor(&other.borrow()) + .into_pyobject(py)? + .into_any() + .unbind()) } /// Reverse-order tensor product. @@ -2136,7 +2172,12 @@ impl SparseObservable { other.get_type().repr()? ))); }; - Ok(other.borrow().tensor(self).into_py(py)) + Ok(other + .borrow() + .tensor(self) + .into_pyobject(py)? + .into_any() + .unbind()) } /// Calculate the adjoint of this observable. @@ -2266,7 +2307,7 @@ impl SparseObservable { return Ok(out); } let (num_qubits, layout) = if layout.is_instance( - &py.import_bound(intern!(py, "qiskit.transpiler"))? + &py.import(intern!(py, "qiskit.transpiler"))? .getattr(intern!(py, "TranspileLayout"))?, )? { ( @@ -2326,8 +2367,8 @@ impl SparseObservable { .get_bound(py) .getattr(intern!(py, "from_symplectic"))? .call1(( - PyArray2::from_owned_array_bound(py, z), - PyArray2::from_owned_array_bound(py, x), + PyArray2::from_owned_array(py, z), + PyArray2::from_owned_array(py, x), )) } } @@ -2649,7 +2690,7 @@ impl ArrayView { ArraySlot::Boundaries => format!("{:?}", obs.boundaries), // Complexes don't have a nice repr in Rust, so just delegate the whole load to Python // and convert back. - ArraySlot::Coeffs => PyList::new_bound(py, &obs.coeffs).repr()?.to_string(), + ArraySlot::Coeffs => PyList::new(py, &obs.coeffs)?.repr()?.to_string(), ArraySlot::BitTerms => format!( "[{}]", obs.bit_terms @@ -2671,22 +2712,25 @@ impl ArrayView { )) } - fn __getitem__(&self, py: Python, index: PySequenceIndex) -> PyResult> { + fn __getitem__<'py>(&'py self, py: Python<'py>, index: PySequenceIndex) -> PyResult> { // The slightly verbose generic setup here is to allow the type of a scalar return to be // different to the type that gets put into the Numpy array, since the `BitTerm` enum can be // a direct scalar, but for Numpy, we need it to be a raw `u8`. - fn get_from_slice( - py: Python, + fn get_from_slice<'py, T, S>( + py: Python<'py>, slice: &[T], index: PySequenceIndex, ) -> PyResult> where - T: ToPyObject + Copy + Into, + T: IntoPyObject<'py> + Copy + Into, + pyo3::PyErr: From<>::Error>, S: ::numpy::Element, { match index.with_len(slice.len())? { - SequenceIndex::Int(index) => Ok(slice[index].to_object(py)), - indices => Ok(PyArray1::from_iter_bound( + SequenceIndex::Int(index) => { + Ok(slice[index].into_pyobject(py)?.into_any().unbind()) + } + indices => Ok(PyArray1::from_iter( py, indices.iter().map(|index| slice[index].into()), ) @@ -2738,7 +2782,7 @@ impl ArrayView { } } else { let values = values - .iter()? + .try_iter()? .map(|value| value?.extract::()?.try_into().map_err(PyErr::from)) .collect::>>()?; if indices.len() != values.len() { @@ -2794,18 +2838,16 @@ impl ArrayView { } let obs = self.base.borrow(py); match self.slot { - ArraySlot::Coeffs => { - cast_array_type(py, PyArray1::from_slice_bound(py, &obs.coeffs), dtype) - } + ArraySlot::Coeffs => cast_array_type(py, PyArray1::from_slice(py, &obs.coeffs), dtype), ArraySlot::Indices => { - cast_array_type(py, PyArray1::from_slice_bound(py, &obs.indices), dtype) + cast_array_type(py, PyArray1::from_slice(py, &obs.indices), dtype) } ArraySlot::Boundaries => { - cast_array_type(py, PyArray1::from_slice_bound(py, &obs.boundaries), dtype) + cast_array_type(py, PyArray1::from_slice(py, &obs.boundaries), dtype) } ArraySlot::BitTerms => { let bit_terms: &[u8] = ::bytemuck::cast_slice(&obs.bit_terms); - cast_array_type(py, PyArray1::from_slice_bound(py, bit_terms), dtype) + cast_array_type(py, PyArray1::from_slice(py, bit_terms), dtype) } } } @@ -2915,18 +2957,20 @@ impl SparseTerm { ) } - fn __getnewargs__(slf_: Bound, py: Python) -> Py { + fn __getnewargs__(slf_: Bound, py: Python) -> PyResult> { let (num_qubits, coeff) = { let slf_ = slf_.borrow(); (slf_.num_qubits, slf_.coeff) }; - ( + Ok(( num_qubits, coeff, Self::get_bit_terms(slf_.clone()), Self::get_indices(slf_), ) - .into_py(py) + .into_pyobject(py)? + .into_any() + .unbind()) } /// Get a copy of this term. @@ -2947,7 +2991,7 @@ impl SparseTerm { // We tie the lifetime of the array to `slf_`, and there are no public ways to modify the // `Box<[BitTerm]>` allocation (including dropping or reallocating it) other than the entire // object getting dropped, which Python will keep safe. - let out = unsafe { PyArray1::borrow_from_array_bound(&arr, slf_.into_any()) }; + let out = unsafe { PyArray1::borrow_from_array(&arr, slf_.into_any()) }; out.readwrite().make_nonwriteable(); out } @@ -2963,7 +3007,7 @@ impl SparseTerm { // We tie the lifetime of the array to `slf_`, and there are no public ways to modify the // `Box<[u32]>` allocation (including dropping or reallocating it) other than the entire // object getting dropped, which Python will keep safe. - let out = unsafe { PyArray1::borrow_from_array_bound(&arr, slf_.into_any()) }; + let out = unsafe { PyArray1::borrow_from_array(&arr, slf_.into_any()) }; out.readwrite().make_nonwriteable(); out } @@ -2988,10 +3032,9 @@ impl SparseTerm { x[*index as usize] = bit_term.has_x_component(); z[*index as usize] = bit_term.has_z_component(); } - PAULI_TYPE.get_bound(py).call1((( - PyArray1::from_vec_bound(py, z), - PyArray1::from_vec_bound(py, x), - ),)) + PAULI_TYPE + .get_bound(py) + .call1(((PyArray1::from_vec(py, z), PyArray1::from_vec(py, x)),)) } } @@ -3004,12 +3047,12 @@ fn cast_array_type<'py, T>( ) -> PyResult> { let base_dtype = array.dtype(); let dtype = dtype - .map(|dtype| PyArrayDescr::new_bound(py, dtype)) + .map(|dtype| PyArrayDescr::new(py, dtype)) .unwrap_or_else(|| Ok(base_dtype.clone()))?; if dtype.is_equiv_to(&base_dtype) { return Ok(array.into_any()); } - PyModule::import_bound(py, intern!(py, "numpy"))? + PyModule::import(py, intern!(py, "numpy"))? .getattr(intern!(py, "array"))? .call( (array,), @@ -3018,7 +3061,7 @@ fn cast_array_type<'py, T>( (intern!(py, "copy"), NUMPY_COPY_ONLY_IF_NEEDED.get_bound(py)), (intern!(py, "dtype"), dtype.as_any()), ] - .into_py_dict_bound(py), + .into_py_dict(py)?, ), ) .map(|obj| obj.into_any()) diff --git a/crates/accelerate/src/sparse_pauli_op.rs b/crates/accelerate/src/sparse_pauli_op.rs index 8c9ffe97e525..f090daf93d1c 100644 --- a/crates/accelerate/src/sparse_pauli_op.rs +++ b/crates/accelerate/src/sparse_pauli_op.rs @@ -69,8 +69,8 @@ pub fn unordered_unique(py: Python, array: PyReadonlyArray2) -> (PyObject, } } ( - indices.into_pyarray_bound(py).into(), - inverses.into_pyarray_bound(py).into(), + indices.into_pyarray(py).into_any().unbind(), + inverses.into_pyarray(py).into_any().unbind(), ) } @@ -410,14 +410,14 @@ pub fn decompose_dense( let array_view = operator.as_array(); let out = py.allow_threads(|| decompose_dense_inner(array_view, tolerance))?; Ok(ZXPaulis { - z: PyArray1::from_vec_bound(py, out.z) + z: PyArray1::from_vec(py, out.z) .reshape([out.phases.len(), out.num_qubits])? .into(), - x: PyArray1::from_vec_bound(py, out.x) + x: PyArray1::from_vec(py, out.x) .reshape([out.phases.len(), out.num_qubits])? .into(), - phases: PyArray1::from_vec_bound(py, out.phases).into(), - coeffs: PyArray1::from_vec_bound(py, out.coeffs).into(), + phases: PyArray1::from_vec(py, out.phases).into(), + coeffs: PyArray1::from_vec(py, out.coeffs).into(), }) } @@ -945,7 +945,7 @@ pub fn to_matrix_dense<'py>( let side = 1usize << paulis.num_qubits(); let parallel = !force_serial && crate::getenv_use_multiple_threads(); let out = to_matrix_dense_inner(&paulis, parallel); - PyArray1::from_vec_bound(py, out).reshape([side, side]) + PyArray1::from_vec(py, out).reshape([side, side]) } /// Inner worker of the Python-exposed [to_matrix_dense]. This is separate primarily to allow @@ -1017,17 +1017,20 @@ pub fn to_matrix_sparse( // This deliberately erases the Rust types in the output so we can return either 32- or 64-bit // indices as appropriate without breaking Rust's typing. - fn to_py_tuple(py: Python, csr_data: CSRData) -> Py + fn to_py_tuple(py: Python, csr_data: CSRData) -> PyResult> where T: numpy::Element, { let (values, indices, indptr) = csr_data; - ( - PyArray1::from_vec_bound(py, values), - PyArray1::from_vec_bound(py, indices), - PyArray1::from_vec_bound(py, indptr), - ) - .into_py(py) + Ok(PyTuple::new( + py, + [ + PyArray1::from_vec(py, values).into_any(), + PyArray1::from_vec(py, indices).into_any(), + PyArray1::from_vec(py, indptr).into_any(), + ], + )? + .unbind()) } // Pessimistic estimation of whether we can fit in `i32`. If there's any risk of overflowing @@ -1041,14 +1044,14 @@ pub fn to_matrix_sparse( } else { to_matrix_sparse_serial_32 }; - Ok(to_py_tuple(py, to_sparse(&paulis))) + to_py_tuple(py, to_sparse(&paulis)) } else { let to_sparse: ToCSRData = if crate::getenv_use_multiple_threads() && !force_serial { to_matrix_sparse_parallel_64 } else { to_matrix_sparse_serial_64 }; - Ok(to_py_tuple(py, to_sparse(&paulis))) + to_py_tuple(py, to_sparse(&paulis)) } } diff --git a/crates/accelerate/src/split_2q_unitaries.rs b/crates/accelerate/src/split_2q_unitaries.rs index ac2577c2fc2c..ee0b3511e438 100644 --- a/crates/accelerate/src/split_2q_unitaries.rs +++ b/crates/accelerate/src/split_2q_unitaries.rs @@ -54,7 +54,7 @@ pub fn split_2q_unitaries( if matches!(decomp.specialization, Specialization::IdEquiv) { let k1r_arr = decomp.K1r(py); let k1l_arr = decomp.K1l(py); - let kwargs = PyDict::new_bound(py); + let kwargs = PyDict::new(py); kwargs.set_item(intern!(py, "num_qubits"), 1)?; let k1r_gate = UNITARY_GATE .get_bound(py) diff --git a/crates/accelerate/src/star_prerouting.rs b/crates/accelerate/src/star_prerouting.rs index dc777a844767..63ae5fd777b3 100644 --- a/crates/accelerate/src/star_prerouting.rs +++ b/crates/accelerate/src/star_prerouting.rs @@ -113,9 +113,9 @@ fn star_preroute( let final_res = ( res.map, - res.node_order.into_pyarray_bound(py).into(), + res.node_order.into_pyarray(py).into_any().unbind(), res.node_block_results, - qubit_mapping.into_pyarray_bound(py).into(), + qubit_mapping.into_pyarray(py).into_any().unbind(), ); final_res diff --git a/crates/accelerate/src/synthesis/clifford/mod.rs b/crates/accelerate/src/synthesis/clifford/mod.rs index dae85de4972d..d2b6b33a4504 100644 --- a/crates/accelerate/src/synthesis/clifford/mod.rs +++ b/crates/accelerate/src/synthesis/clifford/mod.rs @@ -63,7 +63,7 @@ fn random_clifford_tableau( seed: Option, ) -> PyResult>> { let tableau = random_clifford::random_clifford_tableau_inner(num_qubits, seed); - Ok(tableau.into_pyarray_bound(py).unbind()) + Ok(tableau.into_pyarray(py).unbind()) } /// Create a circuit that optimally synthesizes a given Clifford operator represented as diff --git a/crates/accelerate/src/synthesis/linear/mod.rs b/crates/accelerate/src/synthesis/linear/mod.rs index 08a0b1e104b3..a6406adabb48 100644 --- a/crates/accelerate/src/synthesis/linear/mod.rs +++ b/crates/accelerate/src/synthesis/linear/mod.rs @@ -13,6 +13,7 @@ use crate::QiskitError; use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2, PyReadwriteArray2}; use pyo3::prelude::*; +use pyo3::IntoPyObjectExt; mod pmh; pub mod utils; @@ -39,7 +40,7 @@ fn gauss_elimination_with_perm( ) -> PyResult { let matmut = mat.as_array_mut(); let perm = utils::gauss_elimination_with_perm_inner(matmut, ncols, full_elim); - Ok(perm.to_object(py)) + perm.into_py_any(py) } #[pyfunction] @@ -72,7 +73,7 @@ fn gauss_elimination( fn compute_rank_after_gauss_elim(py: Python, mat: PyReadonlyArray2) -> PyResult { let view = mat.as_array(); let rank = utils::compute_rank_after_gauss_elim_inner(view); - Ok(rank.to_object(py)) + rank.into_py_any(py) } #[pyfunction] @@ -84,7 +85,7 @@ fn compute_rank_after_gauss_elim(py: Python, mat: PyReadonlyArray2) -> PyR /// rank: the rank of the matrix fn compute_rank(py: Python, mat: PyReadonlyArray2) -> PyResult { let rank = utils::compute_rank_inner(mat.as_array()); - Ok(rank.to_object(py)) + rank.into_py_any(py) } #[pyfunction] @@ -105,7 +106,7 @@ pub fn calc_inverse_matrix( let view = mat.as_array(); let invmat = utils::calc_inverse_matrix_inner(view, verify.is_some()).map_err(QiskitError::new_err)?; - Ok(invmat.into_pyarray_bound(py).unbind()) + Ok(invmat.into_pyarray(py).unbind()) } #[pyfunction] @@ -126,7 +127,7 @@ pub fn binary_matmul( let view1 = mat1.as_array(); let view2 = mat2.as_array(); let result = utils::binary_matmul_inner(view1, view2).map_err(QiskitError::new_err)?; - Ok(result.into_pyarray_bound(py).unbind()) + Ok(result.into_pyarray(py).unbind()) } #[pyfunction] @@ -159,7 +160,7 @@ fn random_invertible_binary_matrix( seed: Option, ) -> PyResult>> { let matrix = utils::random_invertible_binary_matrix_inner(num_qubits, seed); - Ok(matrix.into_pyarray_bound(py).unbind()) + Ok(matrix.into_pyarray(py).unbind()) } #[pyfunction] @@ -169,10 +170,9 @@ fn random_invertible_binary_matrix( /// mat: a binary matrix. /// Returns: /// bool: True if mat in invertible and False otherwise. -fn check_invertible_binary_matrix(py: Python, mat: PyReadonlyArray2) -> PyResult { +fn check_invertible_binary_matrix(mat: PyReadonlyArray2) -> bool { let view = mat.as_array(); - let out = utils::check_invertible_binary_matrix_inner(view); - Ok(out.to_object(py)) + utils::check_invertible_binary_matrix_inner(view) } pub fn linear(m: &Bound) -> PyResult<()> { diff --git a/crates/accelerate/src/synthesis/mod.rs b/crates/accelerate/src/synthesis/mod.rs index 212e1601ed64..b3647222f8d9 100644 --- a/crates/accelerate/src/synthesis/mod.rs +++ b/crates/accelerate/src/synthesis/mod.rs @@ -20,27 +20,27 @@ mod permutation; use pyo3::prelude::*; pub fn synthesis(m: &Bound) -> PyResult<()> { - let linear_mod = PyModule::new_bound(m.py(), "linear")?; + let linear_mod = PyModule::new(m.py(), "linear")?; linear::linear(&linear_mod)?; m.add_submodule(&linear_mod)?; - let linear_phase_mod = PyModule::new_bound(m.py(), "linear_phase")?; + let linear_phase_mod = PyModule::new(m.py(), "linear_phase")?; linear_phase::linear_phase(&linear_phase_mod)?; m.add_submodule(&linear_phase_mod)?; - let permutation_mod = PyModule::new_bound(m.py(), "permutation")?; + let permutation_mod = PyModule::new(m.py(), "permutation")?; permutation::permutation(&permutation_mod)?; m.add_submodule(&permutation_mod)?; - let clifford_mod = PyModule::new_bound(m.py(), "clifford")?; + let clifford_mod = PyModule::new(m.py(), "clifford")?; clifford::clifford(&clifford_mod)?; m.add_submodule(&clifford_mod)?; - let mc_mod = PyModule::new_bound(m.py(), "multi_controlled")?; + let mc_mod = PyModule::new(m.py(), "multi_controlled")?; multi_controlled::multi_controlled(&mc_mod)?; m.add_submodule(&mc_mod)?; - let evolution_mod = PyModule::new_bound(m.py(), "evolution")?; + let evolution_mod = PyModule::new(m.py(), "evolution")?; evolution::evolution(&evolution_mod)?; m.add_submodule(&evolution_mod)?; diff --git a/crates/accelerate/src/synthesis/permutation/mod.rs b/crates/accelerate/src/synthesis/permutation/mod.rs index 2cc0b02f2c66..daa9d4b02c10 100644 --- a/crates/accelerate/src/synthesis/permutation/mod.rs +++ b/crates/accelerate/src/synthesis/permutation/mod.rs @@ -39,7 +39,7 @@ pub fn _validate_permutation(py: Python, pattern: PyArrayLike1) -> PyResult pub fn _inverse_pattern(py: Python, pattern: PyArrayLike1) -> PyResult { let view = pattern.as_array(); let inverse_i64: Vec = utils::invert(&view).iter().map(|&x| x as i64).collect(); - Ok(inverse_i64.to_object(py)) + Ok(inverse_i64.into_pyobject(py)?.unbind()) } #[pyfunction] diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 324876816a68..42c21e6f8668 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -29,10 +29,11 @@ use pyo3::{ prelude::*, pyclass, types::{PyDict, PyList, PySet, PyTuple}, + IntoPyObjectExt, }; -use qiskit_circuit::circuit_instruction::OperationFromPython; -use qiskit_circuit::operations::{Operation, Param}; +use qiskit_circuit::circuit_instruction::{ExtraInstructionAttributes, OperationFromPython}; +use qiskit_circuit::operations::{Operation, OperationRef, Param}; use qiskit_circuit::packed_instruction::PackedOperation; use smallvec::SmallVec; @@ -57,30 +58,12 @@ type GateMapState = Vec<(String, Vec<(Option, Option for TargetOperation { - fn into_py(self, py: Python<'_>) -> PyObject { - match self { - Self::Normal(normal) => normal.into_py(py), - Self::Variadic(variable) => variable, - } - } -} - -impl ToPyObject for TargetOperation { - fn to_object(&self, py: Python<'_>) -> PyObject { - match self { - Self::Normal(normal) => normal.to_object(py), - Self::Variadic(variable) => variable.clone_ref(py), - } - } -} - impl TargetOperation { /// Gets the number of qubits of a [TargetOperation], will panic if the operation is [TargetOperation::Variadic]. pub fn num_qubits(&self) -> u32 { @@ -112,6 +95,26 @@ pub(crate) struct NormalOperation { op_object: PyObject, } +impl<'py> IntoPyObject<'py> for NormalOperation { + type Target = PyAny; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(self.op_object.bind(py).clone()) + } +} + +impl<'a, 'py> IntoPyObject<'py> for &'a NormalOperation { + type Target = PyAny; + type Output = Borrowed<'a, 'py, Self::Target>; + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(self.op_object.bind_borrowed(py)) + } +} + impl<'py> FromPyObject<'py> for NormalOperation { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let operation: OperationFromPython = ob.extract()?; @@ -123,18 +126,6 @@ impl<'py> FromPyObject<'py> for NormalOperation { } } -impl IntoPy for NormalOperation { - fn into_py(self, py: Python<'_>) -> PyObject { - self.to_object(py) - } -} - -impl ToPyObject for NormalOperation { - fn to_object(&self, py: Python<'_>) -> PyObject { - self.op_object.clone_ref(py) - } -} - /** The base class for a Python ``Target`` object. Contains data representing the constraints of a particular backend. @@ -414,9 +405,11 @@ impl Target { operation: &str, ) -> PyResult>> { match self.qargs_for_operation_name(operation) { - Ok(option_set) => { - Ok(option_set.map(|qargs| qargs.map(|qargs| qargs.to_object(py)).collect())) - } + Ok(option_set) => Ok(option_set.map(|qargs| { + qargs + .map(|qargs| qargs.into_pyobject(py).unwrap().unbind()) + .collect() + })), Err(e) => Err(PyKeyError::new_err(e.message)), } } @@ -431,9 +424,13 @@ impl Target { /// name. This also can also be the class for globally defined variable with /// operations. #[pyo3(name = "operation_from_name")] - pub fn py_operation_from_name(&self, py: Python, instruction: &str) -> PyResult { + pub fn py_operation_from_name<'py>( + &'py self, + py: Python<'py>, + instruction: &str, + ) -> PyResult> { match self._operation_from_name(instruction) { - Ok(instruction) => Ok(instruction.to_object(py)), + Ok(instruction) => instruction.into_pyobject(py), Err(e) => Err(PyKeyError::new_err(e.message)), } } @@ -462,7 +459,14 @@ impl Target { Ok(self .py_operation_names_for_qargs(qargs)? .into_iter() - .map(|x| self._gate_name_map[x].to_object(py)) + .map(|x| { + self._gate_name_map[x] + .into_pyobject(py) + .as_ref() + .unwrap() + .clone() + .unbind() + }) .collect()) } @@ -578,7 +582,7 @@ impl Target { } } TargetOperation::Normal(normal) => { - if python_is_instance(py, normal, _operation_class)? { + if normal.into_pyobject(py)?.is_instance(_operation_class)? { if let Some(parameters) = ¶meters { if parameters.len() != normal.params.len() { continue; @@ -639,7 +643,7 @@ impl Target { let mut matching_params = false; let obj_at_index = &obj_params[index]; if matches!(obj_at_index, Param::ParameterExpression(_)) - || python_compare(py, ¶ms, &obj_params[index])? + || python_compare(py, params, &obj_params[index])? { matching_params = true; } @@ -728,9 +732,11 @@ impl Target { &mut self, py: Python<'_>, strict_direction: bool, - ) -> PyObject { - self.get_non_global_operation_names(strict_direction) - .to_object(py) + ) -> PyResult { + Ok(self + .get_non_global_operation_names(strict_direction) + .into_pyobject(py)? + .unbind()) } // Instance attributes @@ -740,10 +746,10 @@ impl Target { #[pyo3(name = "qargs")] fn py_qargs(&self, py: Python) -> PyResult { if let Some(qargs) = self.qargs() { - let qargs = qargs.map(|qargs| qargs.map(|q| PyTuple::new_bound(py, q))); - let set = PySet::empty_bound(py)?; + let qargs = qargs.map(|qargs| qargs.map(|q| PyTuple::new(py, q))); + let set = PySet::empty(py)?; for qargs in qargs { - set.add(qargs)?; + set.add(qargs.transpose()?)?; } Ok(set.into_any().unbind()) } else { @@ -760,32 +766,46 @@ impl Target { #[getter] #[pyo3(name = "instructions")] pub fn py_instructions(&self, py: Python<'_>) -> PyResult> { - let list = PyList::empty_bound(py); + let list = PyList::empty(py); for (inst, qargs) in self._instructions() { - let qargs = qargs.map(|q| PyTuple::new_bound(py, q).unbind()); - list.append((inst, qargs))?; + let qargs = match qargs { + Some(q) => Some(PyTuple::new(py, q)?.unbind()), + None => None, + }; + let out_inst = match inst { + TargetOperation::Normal(op) => match op.operation.view() { + OperationRef::Standard(standard) => standard + .create_py_op(py, Some(&op.params), &ExtraInstructionAttributes::default())? + .into_any(), + OperationRef::Gate(gate) => gate.gate.clone_ref(py), + OperationRef::Instruction(instruction) => instruction.instruction.clone_ref(py), + OperationRef::Operation(operation) => operation.operation.clone_ref(py), + }, + TargetOperation::Variadic(op_cls) => op_cls.clone_ref(py), + }; + list.append((out_inst, qargs))?; } Ok(list.unbind()) } /// Get the operation names in the target. #[getter] #[pyo3(name = "operation_names")] - fn py_operation_names(&self, py: Python<'_>) -> Py { - PyList::new_bound(py, self.operation_names()).unbind() + fn py_operation_names(&self, py: Python<'_>) -> PyResult> { + Ok(PyList::new(py, self.operation_names())?.unbind()) } /// Get the operation objects in the target. #[getter] #[pyo3(name = "operations")] - fn py_operations(&self, py: Python<'_>) -> Py { - PyList::new_bound(py, self._gate_name_map.values()).unbind() + fn py_operations(&self, py: Python<'_>) -> PyResult> { + Ok(PyList::new(py, self._gate_name_map.values())?.unbind()) } /// Returns a sorted list of physical qubits. #[getter] #[pyo3(name = "physical_qubits")] - fn py_physical_qubits(&self, py: Python<'_>) -> Py { - PyList::new_bound(py, self.physical_qubits()).unbind() + fn py_physical_qubits(&self, py: Python<'_>) -> PyResult> { + Ok(PyList::new(py, self.physical_qubits())?.unbind()) } // Magic methods: @@ -795,7 +815,7 @@ impl Target { } fn __getstate__(&self, py: Python<'_>) -> PyResult> { - let result_list = PyDict::new_bound(py); + let result_list = PyDict::new(py); result_list.set_item("description", self.description.clone())?; result_list.set_item("num_qubits", self.num_qubits)?; result_list.set_item("dt", self.dt)?; @@ -822,9 +842,9 @@ impl Target { ) }) .collect::() - .into_py(py), + .into_pyobject(py)?, )?; - result_list.set_item("gate_name_map", self._gate_name_map.to_object(py))?; + result_list.set_item("gate_name_map", self._gate_name_map.into_pyobject(py)?)?; result_list.set_item("global_operations", self.global_operations.clone())?; result_list.set_item( "qarg_gate_map", @@ -1256,25 +1276,13 @@ fn check_obj_params(parameters: &[Param], obj: &NormalOperation) -> bool { true } -pub fn python_compare(py: Python, obj: &T, other: &U) -> PyResult -where - T: ToPyObject, - U: ToPyObject, -{ - let obj = obj.to_object(py); - let obj_bound = obj.bind(py); - obj_bound.eq(other) -} - -pub fn python_is_instance(py: Python, obj: &T, other: &U) -> PyResult +pub fn python_compare<'a, T, U>(py: Python<'a>, obj: T, other: U) -> PyResult where - T: ToPyObject, - U: ToPyObject, + T: IntoPyObject<'a>, + U: IntoPyObject<'a>, { - let obj = obj.to_object(py); - let other_obj = other.to_object(py); - let obj_bound = obj.bind(py); - obj_bound.is_instance(other_obj.bind(py)) + let obj = obj.into_bound_py_any(py)?; + obj.eq(other.into_bound_py_any(py)?) } pub fn target(m: &Bound) -> PyResult<()> { diff --git a/crates/accelerate/src/target_transpiler/nullable_index_map.rs b/crates/accelerate/src/target_transpiler/nullable_index_map.rs index c48386222fc0..c0f4f18b43e7 100644 --- a/crates/accelerate/src/target_transpiler/nullable_index_map.rs +++ b/crates/accelerate/src/target_transpiler/nullable_index_map.rs @@ -16,8 +16,6 @@ use indexmap::{ IndexMap, }; use pyo3::prelude::*; -use pyo3::types::PyDict; -use pyo3::IntoPy; use rustworkx_core::dictmap::InitWithHasher; use std::ops::Index; use std::{hash::Hash, mem::swap}; @@ -36,7 +34,7 @@ type BaseMap = IndexMap; /// /// **Warning:** This is an experimental feature and should be used with care as it does not /// fully implement all the methods present in `IndexMap` due to API limitations. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, IntoPyObject, IntoPyObjectRef)] pub(crate) struct NullableIndexMap where K: Eq + Hash + Clone, @@ -394,8 +392,8 @@ where impl<'py, K, V> FromPyObject<'py> for NullableIndexMap where - K: IntoPy + FromPyObject<'py> + Eq + Hash + Clone, - V: IntoPy + FromPyObject<'py> + Clone, + K: IntoPyObject<'py> + FromPyObject<'py> + Eq + Hash + Clone, + V: IntoPyObject<'py> + FromPyObject<'py> + Clone, { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let map: IndexMap, V, RandomState> = ob.extract()?; @@ -415,39 +413,3 @@ where }) } } - -impl IntoPy for NullableIndexMap -where - K: IntoPy + Eq + Hash + Clone, - V: IntoPy + Clone, -{ - fn into_py(self, py: Python<'_>) -> PyObject { - let map_object = self.map.into_py(py); - let bound_map_obj = map_object.bind(py); - let downcast_dict: &Bound = bound_map_obj.downcast().unwrap(); - if let Some(null_val) = self.null_val { - downcast_dict - .set_item(py.None(), null_val.into_py(py)) - .unwrap(); - } - map_object - } -} - -impl ToPyObject for NullableIndexMap -where - K: ToPyObject + Eq + Hash + Clone, - V: ToPyObject + Clone, -{ - fn to_object(&self, py: Python<'_>) -> PyObject { - let map_object = self.map.to_object(py); - let bound_map_obj = map_object.bind(py); - let downcast_dict: &Bound = bound_map_obj.downcast().unwrap(); - if let Some(null_val) = &self.null_val { - downcast_dict - .set_item(py.None(), null_val.to_object(py)) - .unwrap(); - } - map_object - } -} diff --git a/crates/accelerate/src/twirling.rs b/crates/accelerate/src/twirling.rs index 91706b5d2824..881e4340f9ea 100644 --- a/crates/accelerate/src/twirling.rs +++ b/crates/accelerate/src/twirling.rs @@ -21,6 +21,7 @@ use pyo3::intern; use pyo3::prelude::*; use pyo3::types::PyList; use pyo3::wrap_pyfunction; +use pyo3::IntoPyObjectExt; use pyo3::Python; use rand::prelude::*; use rand_pcg::Pcg64Mcg; @@ -322,11 +323,11 @@ fn generate_twirled_circuit( custom_gate_map, optimizer_target, )?; - Ok(new_block.into_py(py)) + new_block.into_py_any(py) }) .collect(); let new_blocks = new_blocks?; - let blocks_list = PyList::new_bound( + let blocks_list = PyList::new( py, new_blocks.iter().map(|block| { QUANTUM_CIRCUIT @@ -334,7 +335,7 @@ fn generate_twirled_circuit( .call_method1(intern!(py, "_from_circuit_data"), (block,)) .unwrap() }), - ); + )?; let new_inst_obj = py_inst .instruction @@ -356,8 +357,8 @@ fn generate_twirled_circuit( params: Some(Box::new( new_blocks .iter() - .map(|x| Param::Obj(x.into_py(py))) - .collect::>(), + .map(|x| Ok(Param::Obj(x.clone().into_py_any(py)?))) + .collect::>>()?, )), extra_attrs: inst.extra_attrs.clone(), #[cfg(feature = "cache_pygates")] diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index 4410d6f35e07..82792051a419 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -39,6 +39,7 @@ use pyo3::intern; use pyo3::prelude::*; use pyo3::pybacked::PyBackedStr; use pyo3::types::{PyList, PyTuple, PyType}; +use pyo3::IntoPyObjectExt; use crate::convert_2q_block_matrix::change_basis; use crate::euler_one_qubit_decomposer::{ @@ -211,8 +212,8 @@ fn py_decompose_two_qubit_product_gate( let view = special_unitary.as_array(); let (l, r, phase) = decompose_two_qubit_product_gate(view)?; Ok(( - l.into_pyarray_bound(py).unbind().into(), - r.into_pyarray_bound(py).unbind().into(), + l.into_pyarray(py).into_any().unbind(), + r.into_pyarray(py).into_any().unbind(), phase, )) } @@ -229,8 +230,9 @@ fn weyl_coordinates(py: Python, unitary: PyReadonlyArray2) -> PyObjec let array = unitary.as_array(); __weyl_coordinates(array.into_faer_complex()) .to_vec() - .into_pyarray_bound(py) - .into() + .into_pyarray(py) + .into_any() + .unbind() } fn __weyl_coordinates(unitary: MatRef) -> [f64; 3] { @@ -383,7 +385,7 @@ fn ud(a: f64, b: f64, c: f64) -> Array2 { #[pyo3(name = "Ud")] fn py_ud(py: Python, a: f64, b: f64, c: f64) -> Py> { let ud_mat = ud(a, b, c); - ud_mat.into_pyarray_bound(py).unbind() + ud_mat.into_pyarray(py).unbind() } fn compute_unitary(sequence: &TwoQubitSequenceVec, global_phase: f64) -> Array2 { @@ -461,7 +463,7 @@ impl Specialization { Self::fSimabbEquiv => 8, Self::fSimabmbEquiv => 9, }; - Ok((py.get_type_bound::().getattr("_from_u8")?, (val,)).into_py(py)) + (py.get_type::().getattr("_from_u8")?, (val,)).into_py_any(py) } #[staticmethod] @@ -1084,16 +1086,16 @@ impl TwoQubitWeylDecomposition { } fn __reduce__(&self, py: Python) -> PyResult> { - Ok(( - py.get_type_bound::().getattr("_from_state")?, + ( + py.get_type::().getattr("_from_state")?, ( [self.a, self.b, self.c, self.global_phase], [ - 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.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.specialization, self.default_euler_basis, @@ -1101,7 +1103,7 @@ impl TwoQubitWeylDecomposition { self.requested_fidelity, ), ) - .into_py(py)) + .into_py_any(py) } #[new] @@ -1117,30 +1119,30 @@ impl TwoQubitWeylDecomposition { #[allow(non_snake_case)] #[getter] pub fn K1l(&self, py: Python) -> PyObject { - self.K1l.to_pyarray_bound(py).into() + self.K1l.to_pyarray(py).into_any().unbind() } #[allow(non_snake_case)] #[getter] pub fn K1r(&self, py: Python) -> PyObject { - self.K1r.to_pyarray_bound(py).into() + self.K1r.to_pyarray(py).into_any().unbind() } #[allow(non_snake_case)] #[getter] fn K2l(&self, py: Python) -> PyObject { - self.K2l.to_pyarray_bound(py).into() + self.K2l.to_pyarray(py).into_any().unbind() } #[allow(non_snake_case)] #[getter] fn K2r(&self, py: Python) -> PyObject { - self.K2r.to_pyarray_bound(py).into() + self.K2r.to_pyarray(py).into_any().unbind() } #[getter] fn unitary_matrix(&self, py: Python) -> PyObject { - self.unitary_matrix.to_pyarray_bound(py).into() + self.unitary_matrix.to_pyarray(py).into_any().unbind() } #[pyo3(signature = (euler_basis=None, simplify=false, atol=None))] @@ -1295,11 +1297,16 @@ impl TwoQubitGateSequence { fn __getitem__(&self, py: Python, idx: PySequenceIndex) -> PyResult { match idx.with_len(self.gates.len())? { - SequenceIndex::Int(idx) => Ok(self.gates[idx].to_object(py)), - indices => Ok(PyList::new_bound( + SequenceIndex::Int(idx) => { + let item = &self.gates[idx]; + (item.0, PyList::new(py, &item.1)?, PyList::new(py, &item.2)?).into_py_any(py) + } + indices => Ok(PyList::new( py, - indices.iter().map(|pos| self.gates[pos].to_object(py)), - ) + indices + .iter() + .map(|pos| self.gates[pos].clone().into_pyobject(py).unwrap()), + )? .into_any() .unbind()), } @@ -1965,8 +1972,9 @@ impl TwoQubitBasisDecomposer { self.gate.clone(), self.basis_decomposer .unitary_matrix - .to_pyarray_bound(py) - .into(), + .to_pyarray(py) + .into_any() + .unbind(), self.basis_fidelity, self.euler_basis.as_str(), self.pulse_optimize, @@ -2023,7 +2031,7 @@ impl TwoQubitBasisDecomposer { fn decomp0(py: Python, target: &TwoQubitWeylDecomposition) -> SmallVec<[PyObject; 2]> { decomp0_inner(target) .into_iter() - .map(|x| x.into_pyarray_bound(py).into()) + .map(|x| x.into_pyarray(py).into_any().unbind()) .collect() } @@ -2040,7 +2048,7 @@ impl TwoQubitBasisDecomposer { fn decomp1(&self, py: Python, target: &TwoQubitWeylDecomposition) -> SmallVec<[PyObject; 4]> { self.decomp1_inner(target) .into_iter() - .map(|x| x.into_pyarray_bound(py).into()) + .map(|x| x.into_pyarray(py).into_any().unbind()) .collect() } @@ -2065,7 +2073,7 @@ impl TwoQubitBasisDecomposer { ) -> SmallVec<[PyObject; 6]> { self.decomp2_supercontrolled_inner(target) .into_iter() - .map(|x| x.into_pyarray_bound(py).into()) + .map(|x| x.into_pyarray(py).into_any().unbind()) .collect() } @@ -2080,7 +2088,7 @@ impl TwoQubitBasisDecomposer { ) -> SmallVec<[PyObject; 8]> { self.decomp3_supercontrolled_inner(target) .into_iter() - .map(|x| x.into_pyarray_bound(py).into()) + .map(|x| x.into_pyarray(py).into_any().unbind()) .collect() } @@ -2304,7 +2312,7 @@ fn two_qubit_decompose_up_to_diagonal( Param::Float(circ_seq.global_phase + phase), )?; real_map.mapv_inplace(|x| x.conj()); - Ok((real_map.into_pyarray_bound(py).into(), circ)) + Ok((real_map.into_pyarray(py).into_any().unbind(), circ)) } static MAGIC: GateArray2Q = [ @@ -2500,7 +2508,7 @@ impl TwoQubitControlledUDecomposer { Ok((Some(inv_gate.0), inv_gate_params, qubits)) } RXXEquivalent::CustomPython(gate_cls) => { - let gate_obj = gate_cls.bind(py).call1(PyTuple::new_bound(py, params))?; + let gate_obj = gate_cls.bind(py).call1(PyTuple::new(py, params)?)?; let raw_inverse = gate_obj.call_method0(intern!(py, "inverse"))?; let inverse: OperationFromPython = raw_inverse.extract()?; let params: SmallVec<[f64; 3]> = inverse @@ -2867,7 +2875,7 @@ impl TwoQubitControlledUDecomposer { )), None => { let raw_gate_obj = - gate_cls.bind(py).call1(PyTuple::new_bound(py, params))?; + gate_cls.bind(py).call1(PyTuple::new(py, params)?)?; let op: OperationFromPython = raw_gate_obj.extract()?; Ok(( diff --git a/crates/accelerate/src/uc_gate.rs b/crates/accelerate/src/uc_gate.rs index ec79f4d4d2ba..dd131b7c84e6 100644 --- a/crates/accelerate/src/uc_gate.rs +++ b/crates/accelerate/src/uc_gate.rs @@ -150,9 +150,9 @@ pub fn dec_ucg_help( ( single_qubit_gates .into_iter() - .map(|x| x.into_pyarray_bound(py).into()) + .map(|x| x.into_pyarray(py).into_any().unbind()) .collect(), - diag.into_pyarray_bound(py).into(), + diag.into_pyarray(py).into_any().unbind(), ) } diff --git a/crates/accelerate/src/unitary_synthesis.rs b/crates/accelerate/src/unitary_synthesis.rs index cadec93f09f8..7cbe5ea59f41 100644 --- a/crates/accelerate/src/unitary_synthesis.rs +++ b/crates/accelerate/src/unitary_synthesis.rs @@ -247,7 +247,7 @@ fn py_run_main_loop( .instruction .getattr(py, "blocks")? .bind(py) - .iter()? + .try_iter()? .collect(); let mut new_blocks = Vec::with_capacity(raw_blocks.len()); for raw_block in raw_blocks { @@ -364,7 +364,7 @@ fn py_run_main_loop( // Run 3q+ synthesis _ => { let qs_decomposition: &Bound<'_, PyAny> = imports::QS_DECOMPOSITION.get_bound(py); - let synth_circ = qs_decomposition.call1((unitary.into_pyarray_bound(py),))?; + let synth_circ = qs_decomposition.call1((unitary.into_pyarray(py),))?; let synth_dag = circuit_to_dag( py, QuantumCircuitData::extract_bound(&synth_circ)?, @@ -729,7 +729,7 @@ fn get_2q_decomposers_from_target( fidelity_value *= approx_degree; } let mut embodiment = - xx_embodiments.get_item(op.to_object(py).getattr(py, "base_class")?)?; + xx_embodiments.get_item(op.into_pyobject(py)?.getattr("base_class")?)?; if embodiment.getattr("parameters")?.len()? == 1 { embodiment = embodiment.call_method1("assign_parameters", (vec![strength],))?; @@ -742,11 +742,11 @@ fn get_2q_decomposers_from_target( }, ); - let basis_2q_fidelity_dict = PyDict::new_bound(py); - let embodiments_dict = PyDict::new_bound(py); + let basis_2q_fidelity_dict = PyDict::new(py); + let embodiments_dict = PyDict::new(py); for (strength, fidelity, embodiment) in xx_decomposer_args.flatten() { basis_2q_fidelity_dict.set_item(strength, fidelity)?; - embodiments_dict.set_item(strength, embodiment.into_py(py))?; + embodiments_dict.set_item(strength, embodiment)?; } // Iterate over 2q fidelities and select decomposers @@ -778,7 +778,7 @@ fn get_2q_decomposers_from_target( let decomposer = xx_decomposer.call1(( &basis_2q_fidelity_dict, - PyString::new_bound(py, basis_1q), + PyString::new(py, basis_1q), &embodiments_dict, pi2_decomposer, ))?; @@ -987,10 +987,10 @@ fn synth_su4_dag( .into_iter() .collect(); decomposer - .call_bound( + .call( py, - (su4_mat.clone().into_pyarray_bound(py),), - Some(&kwargs.into_py_dict_bound(py)), + (su4_mat.clone().into_pyarray(py),), + Some(&kwargs.into_py_dict(py)?), )? .extract::(py)? } else { @@ -1053,10 +1053,10 @@ fn reversed_synth_su4_dag( .into_iter() .collect(); decomposer - .call_bound( + .call( py, - (su4_mat.clone().into_pyarray_bound(py),), - Some(&kwargs.into_py_dict_bound(py)), + (su4_mat.clone().into_pyarray(py),), + Some(&kwargs.into_py_dict(py)?), )? .extract::(py)? } else { diff --git a/crates/accelerate/src/utils.rs b/crates/accelerate/src/utils.rs index 598256192f83..c87608f490ab 100644 --- a/crates/accelerate/src/utils.rs +++ b/crates/accelerate/src/utils.rs @@ -37,8 +37,9 @@ pub fn eigenvalues(py: Python, unitary: PyReadonlyArray2>) -> PyObj .into_iter() .map(|x| Complex::::new(x.re, x.im)) .collect::>() - .into_pyarray_bound(py) - .into() + .into_pyarray(py) + .into_any() + .unbind() } pub fn utils(m: &Bound) -> PyResult<()> { diff --git a/crates/circuit/src/bit_data.rs b/crates/circuit/src/bit_data.rs index 56a2560385f2..bf9667e44214 100644 --- a/crates/circuit/src/bit_data.rs +++ b/crates/circuit/src/bit_data.rs @@ -92,7 +92,7 @@ where description, bits: Vec::new(), indices: HashMap::new(), - cached: PyList::empty_bound(py).unbind(), + cached: PyList::empty(py).unbind(), } } @@ -101,7 +101,7 @@ where description, bits: Vec::with_capacity(capacity), indices: HashMap::with_capacity(capacity), - cached: PyList::empty_bound(py).unbind(), + cached: PyList::empty(py).unbind(), } } @@ -189,7 +189,7 @@ where .try_insert(BitAsKey::new(bit), idx.into()) .is_ok() { - self.bits.push(bit.into_py(py)); + self.bits.push(bit.clone().unbind()); self.cached.bind(py).append(bit)?; } else if strict { return Err(PyValueError::new_err(format!( diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index fa36d8b841fb..be36aed73f6b 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -31,6 +31,7 @@ use pyo3::exceptions::{PyRuntimeError, PyTypeError, PyValueError}; use pyo3::prelude::*; use pyo3::pybacked::PyBackedStr; use pyo3::types::{IntoPyDict, PyDict, PyList, PySet, PyTuple, PyType}; +use pyo3::IntoPyObjectExt; use pyo3::{import_exception, intern, PyTraverseError, PyVisit}; use hashbrown::{HashMap, HashSet}; @@ -132,12 +133,12 @@ impl CircuitData { }; self_.set_global_phase(py, global_phase)?; if let Some(qubits) = qubits { - for bit in qubits.iter()? { + for bit in qubits.try_iter()? { self_.add_qubit(py, &bit?, true)?; } } if let Some(clbits) = clbits { - for bit in clbits.iter()? { + for bit in clbits.try_iter()? { self_.add_clbit(py, &bit?, true)?; } } @@ -160,7 +161,7 @@ impl CircuitData { self_.global_phase.clone(), ) }; - Ok((ty, args, None::<()>, self_.iter()?).into_py(py)) + (ty, args, None::<()>, self_.try_iter()?).into_py_any(py) } /// Returns the current sequence of registered :class:`.Qubit` instances as a list. @@ -296,7 +297,7 @@ impl CircuitData { res.param_table.clone_from(&self.param_table); if deepcopy { - let memo = PyDict::new_bound(py); + let memo = PyDict::new(py); for inst in &self.data { res.data.push(PackedInstruction { op: inst.op.py_deepcopy(py, Some(&memo))?, @@ -342,8 +343,8 @@ impl CircuitData { /// Returns: /// tuple[set[:class:`.Qubit`], set[:class:`.Clbit`]]: The active qubits and clbits. pub fn active_bits(&self, py: Python<'_>) -> PyResult> { - let qubits = PySet::empty_bound(py)?; - let clbits = PySet::empty_bound(py)?; + let qubits = PySet::empty(py)?; + 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))?; @@ -353,7 +354,7 @@ impl CircuitData { } } - Ok((qubits, clbits).into_py(py)) + Ok((qubits, clbits).into_pyobject(py)?.unbind()) } /// Invokes callable ``func`` with each instruction's operation. @@ -509,18 +510,23 @@ impl CircuitData { let clbits = self.cargs_interner.get(inst.clbits); CircuitInstruction { operation: inst.op.clone(), - qubits: PyTuple::new_bound(py, self.qubits.map_indices(qubits)).unbind(), - clbits: PyTuple::new_bound(py, self.clbits.map_indices(clbits)).unbind(), + qubits: PyTuple::new(py, self.qubits.map_indices(qubits)) + .unwrap() + .unbind(), + clbits: PyTuple::new(py, self.clbits.map_indices(clbits)) + .unwrap() + .unbind(), params: inst.params_view().iter().cloned().collect(), extra_attrs: inst.extra_attrs.clone(), #[cfg(feature = "cache_pygates")] py_op: inst.py_op.clone(), } - .into_py(py) + .into_py_any(py) + .unwrap() }; match index.with_len(self.data.len())? { SequenceIndex::Int(index) => Ok(get_single(index)), - indices => Ok(PyList::new_bound(py, indices.iter().map(get_single)).into_py(py)), + indices => PyList::new(py, indices.iter().map(get_single))?.into_py_any(py), } } @@ -546,7 +552,7 @@ impl CircuitData { step: 1, } => { // `list` allows setting a slice with step +1 to an arbitrary length. - let values = value.iter()?.collect::>>()?; + let values = value.try_iter()?.collect::>>()?; for (index, value) in indices.iter().zip(values.iter()) { set_single(self, index, value)?; } @@ -567,7 +573,7 @@ impl CircuitData { Ok(()) } indices => { - let values = value.iter()?.collect::>>()?; + let values = value.try_iter()?.collect::>>()?; if indices.len() == values.len() { for (index, value) in indices.iter().zip(values.iter()) { set_single(self, index, value)?; @@ -648,7 +654,7 @@ impl CircuitData { instruction: instruction_index, parameter: parameter_index, }; - for param in parameters.iter()? { + for param in parameters.try_iter()? { self.param_table.track(¶m?, Some(usage))?; } } @@ -699,7 +705,7 @@ impl CircuitData { } return Ok(()); } - for v in itr.iter()? { + for v in itr.try_iter()? { self.append(v?.downcast()?)?; } Ok(()) @@ -728,7 +734,7 @@ impl CircuitData { ) } else { let values = sequence - .iter()? + .try_iter()? .map(|ob| Param::extract_no_coerce(&ob?)) .collect::>>()?; self.assign_parameters_from_slice(sequence.py(), &values) @@ -742,7 +748,7 @@ impl CircuitData { fn assign_parameters_mapping(&mut self, mapping: Bound) -> PyResult<()> { let py = mapping.py(); let mut items = Vec::new(); - for item in mapping.call_method0("items")?.iter()? { + for item in mapping.call_method0("items")?.try_iter()? { let (param_ob, value) = item?.extract::<(Py, AssignParam)>()?; let uuid = ParameterUuid::from_parameter(param_ob.bind(py))?; // It's fine if the mapping contains parameters that we don't have - just skip those. @@ -798,8 +804,8 @@ impl CircuitData { // Implemented using generic iterators on both sides // for simplicity. - let mut ours_itr = slf.iter()?; - let mut theirs_itr = other.iter()?; + let mut ours_itr = slf.try_iter()?; + let mut theirs_itr = other.try_iter()?; loop { match (ours_itr.next(), theirs_itr.next()) { (Some(ours), Some(theirs)) => { @@ -849,7 +855,11 @@ impl CircuitData { #[setter] pub fn set_global_phase(&mut self, py: Python, angle: Param) -> PyResult<()> { if let Param::ParameterExpression(expr) = &self.global_phase { - for param_ob in expr.bind(py).getattr(intern!(py, "parameters"))?.iter()? { + for param_ob in expr + .bind(py) + .getattr(intern!(py, "parameters"))? + .try_iter()? + { match self.param_table.remove_use( ParameterUuid::from_parameter(¶m_ob?)?, ParameterUse::GlobalPhase, @@ -1331,7 +1341,7 @@ impl CircuitData { value: &Param, coerce: bool| -> PyResult { - let new_expr = expr.call_method1(assign_attr, (param_ob, value.to_object(py)))?; + let new_expr = expr.call_method1(assign_attr, (param_ob, value.into_py_any(py)?))?; if new_expr.getattr(parameters_attr)?.len()? == 0 { let out = new_expr.call_method0(numeric_attr)?; if coerce { @@ -1448,10 +1458,10 @@ impl CircuitData { Param::extract_no_coerce( &obj.call_method( assign_parameters_attr, - ([(¶m_ob, value.as_ref())].into_py_dict_bound(py),), + ([(¶m_ob, value.as_ref())].into_py_dict(py)?,), Some( &[("inplace", false), ("flat_input", true)] - .into_py_dict_bound(py), + .into_py_dict(py)?, ), )?, )? @@ -1464,7 +1474,7 @@ impl CircuitData { previous.extra_attrs = new_op.extra_attrs; #[cfg(feature = "cache_pygates")] { - previous.py_op = op.into_py(py).into(); + previous.py_op = op.unbind().into(); } for uuid in uuids.iter() { self.param_table.add_use(*uuid, usage)? @@ -1476,7 +1486,9 @@ impl CircuitData { } let assign_kwargs = (!user_operations.is_empty()).then(|| { - [("inplace", true), ("flat_input", true), ("strict", false)].into_py_dict_bound(py) + [("inplace", true), ("flat_input", true), ("strict", false)] + .into_py_dict(py) + .unwrap() }); for (instruction, bindings) in user_operations { // We only put non-standard gates in `user_operations`, so we're not risking creating a @@ -1504,7 +1516,7 @@ impl CircuitData { if !definition_cache.is_none() { definition_cache.call_method( assign_parameters_attr, - (bindings.into_py_dict_bound(py),), + (bindings.into_py_dict(py)?.into_any().unbind(),), assign_kwargs.as_ref(), )?; } diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index e449ad660e38..3ded8da806a5 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -13,13 +13,15 @@ #[cfg(feature = "cache_pygates")] use std::sync::OnceLock; -use numpy::IntoPyArray; +use numpy::{IntoPyArray, PyArray2}; use pyo3::basic::CompareOp; use pyo3::exceptions::{PyDeprecationWarning, PyTypeError}; use pyo3::prelude::*; -use pyo3::types::{PyList, PyString, PyTuple, PyType}; -use pyo3::{intern, IntoPy, PyObject, PyResult}; +use pyo3::types::{PyBool, PyList, PyString, PyTuple, PyType}; +use pyo3::IntoPyObjectExt; +use pyo3::{intern, PyObject, PyResult}; +use num_complex::Complex64; use smallvec::SmallVec; use crate::imports::{ @@ -80,7 +82,7 @@ impl ExtraInstructionAttributes { attrs .unit .as_deref() - .map(|unit| <&str as IntoPy>>::into_py(unit, py)) + .map(|unit| unit.into_pyobject(py).unwrap().unbind()) }) .unwrap_or_else(|| Self::default_unit(py).clone().unbind()) } @@ -281,7 +283,7 @@ impl CircuitInstruction { params: op_parts.params, extra_attrs: op_parts.extra_attrs, #[cfg(feature = "cache_pygates")] - py_op: operation.into_py(py).into(), + py_op: operation.clone().unbind().into(), }) } @@ -297,7 +299,7 @@ impl CircuitInstruction { Ok(Self { operation: standard.into(), qubits: as_tuple(py, qubits)?.unbind(), - clbits: PyTuple::empty_bound(py).unbind(), + clbits: PyTuple::empty(py).unbind(), params, extra_attrs: ExtraInstructionAttributes::new(label, None, None, None), #[cfg(feature = "cache_pygates")] @@ -347,19 +349,19 @@ impl CircuitInstruction { /// Returns the Instruction name corresponding to the op for this node #[getter] - fn get_name(&self, py: Python) -> PyObject { - self.operation.name().to_object(py) + fn get_name(&self) -> &str { + self.operation.name() } #[getter] - fn get_params(&self, py: Python) -> PyObject { - self.params.to_object(py) + fn get_params(&self) -> &[Param] { + self.params.as_slice() } #[getter] - fn matrix(&self, py: Python) -> Option { + fn matrix<'py>(&'py self, py: Python<'py>) -> Option>> { let matrix = self.operation.view().matrix(&self.params); - matrix.map(|mat| mat.into_pyarray_bound(py).into()) + matrix.map(move |mat| mat.into_pyarray(py)) } #[getter] @@ -454,7 +456,7 @@ impl CircuitInstruction { params: params.unwrap_or(op_parts.params), extra_attrs: op_parts.extra_attrs, #[cfg(feature = "cache_pygates")] - py_op: operation.into_py(py).into(), + py_op: operation.clone().unbind().into(), }) } else { Ok(Self { @@ -470,12 +472,12 @@ impl CircuitInstruction { } pub fn __getnewargs__(&self, py: Python<'_>) -> PyResult { - Ok(( + ( self.get_operation(py)?, self.qubits.bind(py), self.clbits.bind(py), ) - .into_py(py)) + .into_py_any(py) } pub fn __repr__(self_: &Bound, py: Python<'_>) -> PyResult { @@ -497,24 +499,30 @@ impl CircuitInstruction { // 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<'py>(&self, py: Python<'py>) -> PyResult> { - Ok(PyTuple::new_bound( + PyTuple::new( py, [ self.get_operation(py)?, self.qubits.bind(py).to_list().into(), self.clbits.bind(py).to_list().into(), ], - )) + ) } pub fn __getitem__(&self, py: Python<'_>, key: &Bound) -> PyResult { warn_on_legacy_circuit_instruction_iteration(py)?; - Ok(self._legacy_format(py)?.as_any().get_item(key)?.into_py(py)) + self._legacy_format(py)? + .as_any() + .get_item(key)? + .into_py_any(py) } pub fn __iter__(&self, py: Python<'_>) -> PyResult { warn_on_legacy_circuit_instruction_iteration(py)?; - Ok(self._legacy_format(py)?.as_any().iter()?.into_py(py)) + self._legacy_format(py)? + .as_any() + .try_iter()? + .into_py_any(py) } pub fn __len__(&self, py: Python) -> PyResult { @@ -582,15 +590,17 @@ impl CircuitInstruction { )) } - match op { - CompareOp::Eq => Ok(eq(py, self_, other)? - .map(|b| b.into_py(py)) - .unwrap_or_else(|| py.NotImplemented())), - CompareOp::Ne => Ok(eq(py, self_, other)? - .map(|b| (!b).into_py(py)) - .unwrap_or_else(|| py.NotImplemented())), - _ => Ok(py.NotImplemented()), - } + Ok(match op { + CompareOp::Eq => match eq(py, self_, other)? { + Some(res) => PyBool::new(py, res).to_owned().into_any().unbind(), + None => py.NotImplemented(), + }, + CompareOp::Ne => match eq(py, self_, other)? { + Some(res) => PyBool::new(py, !res).to_owned().into_any().unbind(), + None => py.NotImplemented(), + }, + _ => py.NotImplemented(), + }) } } @@ -702,7 +712,7 @@ impl<'py> FromPyObject<'py> for OperationFromPython { clbits: 0, params: params.len() as u32, op_name: ob.getattr(intern!(py, "name"))?.extract()?, - gate: ob.into_py(py), + gate: ob.clone().unbind(), }); return Ok(OperationFromPython { operation: PackedOperation::from_gate(gate), @@ -718,7 +728,7 @@ impl<'py> FromPyObject<'py> for OperationFromPython { params: params.len() as u32, op_name: ob.getattr(intern!(py, "name"))?.extract()?, control_flow: ob.is_instance(CONTROL_FLOW_OP.get_bound(py))?, - instruction: ob.into_py(py), + instruction: ob.clone().unbind(), }); return Ok(OperationFromPython { operation: PackedOperation::from_instruction(instruction), @@ -733,7 +743,7 @@ impl<'py> FromPyObject<'py> for OperationFromPython { clbits: ob.getattr(intern!(py, "num_clbits"))?.extract()?, params: params.len() as u32, op_name: ob.getattr(intern!(py, "name"))?.extract()?, - operation: ob.into_py(py), + operation: ob.clone().unbind(), }); return Ok(OperationFromPython { operation: PackedOperation::from_operation(operation), @@ -748,7 +758,7 @@ impl<'py> FromPyObject<'py> for OperationFromPython { /// Convert a sequence-like Python object to a tuple. fn as_tuple<'py>(py: Python<'py>, seq: Option>) -> PyResult> { let Some(seq) = seq else { - return Ok(PyTuple::empty_bound(py)); + return Ok(PyTuple::empty(py)); }; if seq.is_instance_of::() { Ok(seq.downcast_into_exact::()?) @@ -756,12 +766,12 @@ fn as_tuple<'py>(py: Python<'py>, seq: Option>) -> PyResult()?.to_tuple()) } else { // New tuple from iterable. - Ok(PyTuple::new_bound( + PyTuple::new( py, - seq.iter()? + seq.try_iter()? .map(|o| Ok(o?.unbind())) .collect::>>()?, - )) + ) } } @@ -783,7 +793,7 @@ fn warn_on_legacy_circuit_instruction_iteration(py: Python) -> PyResult<()> { " Instead, use the `operation`, `qubits` and `clbits` named attributes." ) ), - py.get_type_bound::(), + py.get_type::(), // Stack level. Compared to Python-space calls to `warn`, this is unusually low // beacuse all our internal call structure is now Rust-space and invisible to Python. 1, diff --git a/crates/circuit/src/converters.rs b/crates/circuit/src/converters.rs index 030a582bdad7..1aeb24a7b3df 100644 --- a/crates/circuit/src/converters.rs +++ b/crates/circuit/src/converters.rs @@ -62,15 +62,15 @@ impl<'py> FromPyObject<'py> for QuantumCircuitData<'py> { .ok(), input_vars: ob .call_method0(intern!(py, "iter_input_vars"))? - .iter()? + .try_iter()? .collect::>>()?, captured_vars: ob .call_method0(intern!(py, "iter_captured_vars"))? - .iter()? + .try_iter()? .collect::>>()?, declared_vars: ob .call_method0(intern!(py, "iter_declared_vars"))? - .iter()? + .try_iter()? .collect::>>()?, }) } diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 9b2a78127826..3c32358f9fbb 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -40,6 +40,7 @@ use pyo3::exceptions::{ }; use pyo3::intern; use pyo3::prelude::*; +use pyo3::IntoPyObjectExt; use pyo3::types::{ IntoPyDict, PyDict, PyInt, PyIterator, PyList, PySequence, PySet, PyString, PyTuple, PyType, @@ -146,13 +147,13 @@ pub enum Wire { } impl Wire { - fn to_pickle(&self, py: Python) -> PyObject { + fn to_pickle(&self, py: Python) -> PyResult { match self { - Self::Qubit(bit) => (0, bit.0.into_py(py)), - Self::Clbit(bit) => (1, bit.0.into_py(py)), - Self::Var(var) => (2, var.0.into_py(py)), + Self::Qubit(bit) => (0, bit.0.into_py_any(py)?), + Self::Clbit(bit) => (1, bit.0.into_py_any(py)?), + Self::Var(var) => (2, var.0.into_py_any(py)?), } - .into_py(py) + .into_py_any(py) } fn from_pickle(b: &Bound) -> PyResult { @@ -255,7 +256,7 @@ struct PyLegacyResources { impl PyControlFlowModule { fn new(py: Python) -> PyResult { - let module = PyModule::import_bound(py, "qiskit.circuit.controlflow")?; + let module = PyModule::import(py, "qiskit.circuit.controlflow")?; Ok(PyControlFlowModule { condition_resources: module.getattr("condition_resources")?.unbind(), node_resources: module.getattr("node_resources")?.unbind(), @@ -282,6 +283,7 @@ impl PyControlFlowModule { } } +#[derive(IntoPyObject)] struct PyVariableMapper { mapper: Py, } @@ -301,7 +303,7 @@ impl PyVariableMapper { .get_bound(py) .call( (target_cregs, bit_map, var_map), - Some(&kwargs.into_py_dict_bound(py)), + Some(&kwargs.into_py_dict(py)?), )? .unbind(), }) @@ -313,12 +315,11 @@ impl PyVariableMapper { allow_reorder: bool, ) -> PyResult> { let py = condition.py(); - let kwargs: HashMap<&str, Py> = - HashMap::from_iter([("allow_reorder", allow_reorder.into_py(py))]); + let kwargs: HashMap<&str, bool> = HashMap::from_iter([("allow_reorder", allow_reorder)]); self.mapper.bind(py).call_method( intern!(py, "map_condition"), (condition,), - Some(&kwargs.into_py_dict_bound(py)), + Some(&kwargs.into_py_dict(py)?), ) } @@ -330,12 +331,6 @@ impl PyVariableMapper { } } -impl IntoPy> for PyVariableMapper { - fn into_py(self, _py: Python<'_>) -> Py { - self.mapper - } -} - #[pyfunction] fn reject_new_register(reg: &Bound) -> PyResult<()> { Err(DAGCircuitError::new_err(format!( @@ -374,11 +369,11 @@ impl DAGCircuit { pub fn new(py: Python<'_>) -> PyResult { Ok(DAGCircuit { name: None, - metadata: Some(PyDict::new_bound(py).unbind().into()), + metadata: Some(PyDict::new(py).unbind().into()), calibrations: HashMap::new(), dag: StableDiGraph::default(), - qregs: PyDict::new_bound(py).unbind(), - cregs: PyDict::new_bound(py).unbind(), + qregs: PyDict::new(py).unbind(), + cregs: PyDict::new(py).unbind(), qargs_interner: Interner::new(), cargs_interner: Interner::new(), qubits: BitData::new(py, "qubits".to_string()), @@ -387,8 +382,8 @@ impl DAGCircuit { global_phase: Param::Float(0.), duration: None, unit: "dt".to_string(), - qubit_locations: PyDict::new_bound(py).unbind(), - clbit_locations: PyDict::new_bound(py).unbind(), + qubit_locations: PyDict::new(py).unbind(), + clbit_locations: PyDict::new(py).unbind(), qubit_io_map: Vec::new(), clbit_io_map: Vec::new(), var_io_map: Vec::new(), @@ -396,9 +391,9 @@ impl DAGCircuit { control_flow_module: PyControlFlowModule::new(py)?, vars_info: HashMap::new(), vars_by_type: [ - PySet::empty_bound(py)?.unbind(), - PySet::empty_bound(py)?.unbind(), - PySet::empty_bound(py)?.unbind(), + PySet::empty(py)?.unbind(), + PySet::empty(py)?.unbind(), + PySet::empty(py)?.unbind(), ], }) } @@ -417,7 +412,7 @@ impl DAGCircuit { "deprecated as of Qiskit 1.3.0. It will be removed in Qiskit 2.0.0.", ) ), - py.get_type_bound::(), + py.get_type::(), 2, ))?; Ok(self.duration.as_ref().map(|x| x.clone_ref(py))) @@ -436,7 +431,7 @@ impl DAGCircuit { "deprecated as of Qiskit 1.3.0. It will be removed in Qiskit 2.0.0.", ) ), - py.get_type_bound::(), + py.get_type::(), 2, ))?; Ok(self.unit.clone()) @@ -444,7 +439,7 @@ impl DAGCircuit { #[getter] fn input_map(&self, py: Python) -> PyResult> { - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (qubit, indices) in self .qubit_io_map .iter() @@ -483,7 +478,7 @@ impl DAGCircuit { #[getter] fn output_map(&self, py: Python) -> PyResult> { - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (qubit, indices) in self .qubit_io_map .iter() @@ -521,7 +516,7 @@ impl DAGCircuit { } fn __getstate__(&self, py: Python) -> PyResult> { - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); out_dict.set_item("name", self.name.as_ref().map(|x| x.clone_ref(py)))?; out_dict.set_item("metadata", self.metadata.as_ref().map(|x| x.clone_ref(py)))?; out_dict.set_item("_calibrations_prop", self.calibrations.clone())?; @@ -534,7 +529,7 @@ impl DAGCircuit { .iter() .enumerate() .map(|(k, v)| (k, [v[0].index(), v[1].index()])) - .into_py_dict_bound(py), + .into_py_dict(py)?, )?; out_dict.set_item( "clbit_io_map", @@ -542,7 +537,7 @@ impl DAGCircuit { .iter() .enumerate() .map(|(k, v)| (k, [v[0].index(), v[1].index()])) - .into_py_dict_bound(py), + .into_py_dict(py)?, )?; out_dict.set_item( "var_io_map", @@ -550,7 +545,7 @@ impl DAGCircuit { .iter() .enumerate() .map(|(k, v)| (k, [v[0].index(), v[1].index()])) - .into_py_dict_bound(py), + .into_py_dict(py)?, )?; out_dict.set_item("op_name", self.op_names.clone())?; out_dict.set_item( @@ -568,7 +563,7 @@ impl DAGCircuit { ), ) }) - .into_py_dict_bound(py), + .into_py_dict(py)?, )?; out_dict.set_item("vars_by_type", self.vars_by_type.clone())?; out_dict.set_item("qubits", self.qubits.bits())?; @@ -577,7 +572,7 @@ impl DAGCircuit { let mut nodes: Vec = Vec::with_capacity(self.dag.node_count()); for node_idx in self.dag.node_indices() { let node_data = self.get_node(py, node_idx)?; - nodes.push((node_idx.index(), node_data).to_object(py)); + nodes.push((node_idx.index(), node_data).into_py_any(py)?); } out_dict.set_item("nodes", nodes)?; out_dict.set_item( @@ -594,9 +589,9 @@ impl DAGCircuit { ( endpoints.0.index(), endpoints.1.index(), - edge_w.clone().to_pickle(py), + edge_w.clone().to_pickle(py)?, ) - .to_object(py) + .into_py_any(py)? } None => py.None(), }; @@ -792,7 +787,7 @@ impl DAGCircuit { .iter() .chain(self.clbits.bits().iter()) .collect(); - let out_list = PyList::new_bound(py, wires); + let out_list = PyList::new(py, wires)?; for var_type_set in &self.vars_by_type { for var in var_type_set.bind(py).iter() { out_list.append(var)?; @@ -910,9 +905,10 @@ impl DAGCircuit { } let params_tuple = if let Some(operands) = params { - let add_calibration = PyModule::from_code_bound( + let add_calibration = PyModule::from_code( py, - r#" + std::ffi::CString::new( + r#" import numpy as np def _format(operand): @@ -932,29 +928,32 @@ def _format(operand): # Unassigned parameter return operand "#, - "add_calibration.py", - "add_calibration", + )? + .as_c_str(), + std::ffi::CString::new("add_calibration.py")?.as_c_str(), + std::ffi::CString::new("add_calibration")?.as_c_str(), )?; let format = add_calibration.getattr("_format")?; - let mapped: PyResult> = operands.iter()?.map(|p| format.call1((p?,))).collect(); - PyTuple::new_bound(py, mapped?).into_any() + let mapped: PyResult> = + operands.try_iter()?.map(|p| format.call1((p?,))).collect(); + PyTuple::new(py, mapped?)?.into_any() } else { - PyTuple::empty_bound(py).into_any() + PyTuple::empty(py).into_any() }; let calibrations = self .calibrations .entry(gate.extract()?) - .or_insert_with(|| PyDict::new_bound(py).unbind()) + .or_insert_with(|| PyDict::new(py).unbind()) .bind(py); let qubits = if let Ok(qubits) = qubits.downcast::() { qubits.to_tuple()?.into_any() } else { - PyTuple::new_bound(py, [qubits]).into_any() + PyTuple::new(py, [qubits])?.into_any() }; - let key = PyTuple::new_bound(py, &[qubits.unbind(), params_tuple.into_any().unbind()]); + let key = PyTuple::new(py, &[qubits.unbind(), params_tuple.into_any().unbind()])?; calibrations.set_item(key, schedule)?; Ok(()) } @@ -989,18 +988,18 @@ def _format(operand): continue; } } - params.push(p.to_object(py)); + params.push(p.into_py_any(py)?); } let qubits: Vec = self .qubits .map_bits(node.instruction.qubits.bind(py).iter())? .map(|bit| bit.0) .collect(); - let qubits = PyTuple::new_bound(py, qubits); - let params = PyTuple::new_bound(py, params); + let qubits = PyTuple::new(py, qubits)?; + let params = PyTuple::new(py, params)?; self.calibrations[node.instruction.operation.name()] .bind(py) - .contains((qubits, params).to_object(py)) + .contains((qubits, params)) } /// Remove all operation nodes with the given name. @@ -1075,7 +1074,7 @@ def _format(operand): } self.qregs.bind(py).set_item(®ister_name, qreg)?; - for (index, bit) in qreg.iter()?.enumerate() { + for (index, bit) in qreg.try_iter()?.enumerate() { let bit = bit?; if self.qubits.find(&bit).is_none() { self.add_qubit_unchecked(py, &bit)?; @@ -1108,7 +1107,7 @@ def _format(operand): } self.cregs.bind(py).set_item(register_name, creg)?; - for (index, bit) in creg.iter()?.enumerate() { + for (index, bit) in creg.try_iter()?.enumerate() { let bit = bit?; if self.clbits.find(&bit).is_none() { self.add_clbit_unchecked(py, &bit)?; @@ -1141,7 +1140,11 @@ def _format(operand): /// Raises: /// DAGCircuitError: If the supplied :obj:`~Bit` was of an unknown type. /// DAGCircuitError: If the supplied :obj:`~Bit` could not be found on the circuit. - fn find_bit<'py>(&self, py: Python<'py>, bit: &Bound) -> PyResult> { + fn find_bit<'py>( + &self, + py: Python<'py>, + bit: &Bound<'py, PyAny>, + ) -> PyResult> { if bit.is_instance(imports::QUBIT.get_bound(py))? { return self.qubit_locations.bind(py).get_item(bit)?.ok_or_else(|| { DAGCircuitError::new_err(format!( @@ -1222,7 +1225,7 @@ def _format(operand): // Remove any references to bits. let mut cregs_to_remove = Vec::new(); for creg in self.cregs.bind(py).values() { - for bit in creg.iter()? { + for bit in creg.try_iter()? { let bit = bit?; if clbits.contains(&self.clbits.find(&bit).unwrap()) { cregs_to_remove.push(creg); @@ -1230,7 +1233,7 @@ def _format(operand): } } } - self.remove_cregs(py, &PyTuple::new_bound(py, cregs_to_remove))?; + self.remove_cregs(py, &PyTuple::new(py, cregs_to_remove)?)?; // Remove DAG in/out nodes etc. for bit in clbits.iter() { @@ -1354,7 +1357,7 @@ def _format(operand): self.cregs .bind(py) .del_item(creg.getattr(intern!(py, "name"))?)?; - for (i, bit) in creg.iter()?.enumerate() { + for (i, bit) in creg.try_iter()?.enumerate() { let bit = bit?; let bit_position = self .clbit_locations @@ -1430,7 +1433,7 @@ def _format(operand): // Remove any references to bits. let mut qregs_to_remove = Vec::new(); for qreg in self.qregs.bind(py).values() { - for bit in qreg.iter()? { + for bit in qreg.try_iter()? { let bit = bit?; if qubits.contains(&self.qubits.find(&bit).unwrap()) { qregs_to_remove.push(qreg); @@ -1438,7 +1441,7 @@ def _format(operand): } } } - self.remove_qregs(py, &PyTuple::new_bound(py, qregs_to_remove))?; + self.remove_qregs(py, &PyTuple::new(py, qregs_to_remove)?)?; // Remove DAG in/out nodes etc. for bit in qubits.iter() { @@ -1562,7 +1565,7 @@ def _format(operand): self.qregs .bind(py) .del_item(qreg.getattr(intern!(py, "name"))?)?; - for (i, bit) in qreg.iter()?.enumerate() { + for (i, bit) in qreg.try_iter()?.enumerate() { let bit = bit?; let bit_position = self .qubit_locations @@ -1896,13 +1899,13 @@ def _format(operand): .bits() .iter() .zip(slf.qubits.bits()) - .into_py_dict_bound(py); + .into_py_dict(py)?; let identity_clbit_map = other .clbits .bits() .iter() .zip(slf.clbits.bits()) - .into_py_dict_bound(py); + .into_py_dict(py)?; let qubit_map: Bound = match qubits { None => identity_qubit_map.clone(), @@ -1916,7 +1919,7 @@ def _format(operand): let self_qubits = slf.qubits.cached().bind(py); let other_qubits = other.qubits.cached().bind(py); - let dict = PyDict::new_bound(py); + let dict = PyDict::new(py); for (i, q) in qubits.iter().enumerate() { let q = if q.is_instance_of::() { self_qubits.get_item(q.extract()?)? @@ -1942,7 +1945,7 @@ def _format(operand): let self_clbits = slf.clbits.cached().bind(py); let other_clbits = other.clbits.cached().bind(py); - let dict = PyDict::new_bound(py); + let dict = PyDict::new(py); for (i, q) in clbits.iter().enumerate() { let q = if q.is_instance_of::() { self_clbits.get_item(q.extract()?)? @@ -1961,18 +1964,15 @@ def _format(operand): identity_qubit_map .iter() .chain(identity_clbit_map.iter()) - .into_py_dict_bound(py) + .into_py_dict(py)? } else { - qubit_map - .iter() - .chain(clbit_map.iter()) - .into_py_dict_bound(py) + qubit_map.iter().chain(clbit_map.iter()).into_py_dict(py)? }; // Chck duplicates in wire map. { let edge_map_values: Vec<_> = edge_map.values().iter().collect(); - if PySet::new_bound(py, edge_map_values.as_slice())?.len() != edge_map.len() { + if PySet::new(py, edge_map_values.as_slice())?.len() != edge_map.len() { return Err(DAGCircuitError::new_err("duplicates in wire_map")); } } @@ -1991,7 +1991,7 @@ def _format(operand): Some(calibrations) => calibrations, None => { dag.calibrations - .insert(gate.clone(), PyDict::new_bound(py).unbind()); + .insert(gate.clone(), PyDict::new(py).unbind()); &dag.calibrations[gate] } }; @@ -2030,7 +2030,7 @@ def _format(operand): dag.cregs.bind(py).values().into_any(), Some(edge_map.clone()), None, - Some(wrap_pyfunction_bound!(reject_new_register, py)?.to_object(py)), + Some(wrap_pyfunction!(reject_new_register, py)?.into_py_any(py)?), )?; for node in other.topological_nodes()? { @@ -2077,7 +2077,7 @@ def _format(operand): .unwrap_or_else(|| bit.bind(py).clone()), ); } - PyTuple::new_bound(py, mapped) + PyTuple::new(py, mapped) }; let m_cargs = { let clbits = other @@ -2091,7 +2091,7 @@ def _format(operand): .unwrap_or_else(|| bit.bind(py).clone()), ); } - PyTuple::new_bound(py, mapped) + PyTuple::new(py, mapped) }; // We explicitly create a mutable py_op here since we might @@ -2122,8 +2122,8 @@ def _format(operand): dag.py_apply_operation_back( py, py_op, - Some(TupleLikeArg { value: m_qargs }), - Some(TupleLikeArg { value: m_cargs }), + Some(TupleLikeArg { value: m_qargs? }), + Some(TupleLikeArg { value: m_cargs? }), false, )?; } @@ -2136,7 +2136,8 @@ def _format(operand): } if !inplace { - Ok(Some(dag.into_py(py))) + let out_obj = dag.into_py_any(py)?; + Ok(Some(out_obj)) } else { Ok(None) } @@ -2146,7 +2147,7 @@ def _format(operand): /// /// Returns: /// DAGCircuit: the reversed dag. - fn reverse_ops<'py>(slf: PyRef, py: Python<'py>) -> PyResult> { + fn reverse_ops<'py>(slf: PyRef<'py, Self>, py: Python<'py>) -> PyResult> { let qc = imports::DAG_TO_CIRCUIT.get_bound(py).call1((slf,))?; let reversed = qc.call_method0("reverse_ops")?; imports::CIRCUIT_TO_DAG.get_bound(py).call1((reversed,)) @@ -2207,7 +2208,7 @@ def _format(operand): } } } - Ok(PyTuple::new_bound(py, result).into_any().iter()?.unbind()) + Ok(PyTuple::new(py, result)?.into_any().try_iter()?.unbind()) } /// Return the number of operations. If there is control flow present, this count may only @@ -2267,7 +2268,7 @@ def _format(operand): || inst_bound.is_instance(imports::SWITCH_CASE_OP.get_bound(py))? { let blocks = inst_bound.getattr("blocks")?; - for block in blocks.iter()? { + for block in blocks.try_iter()? { let inner_dag: &DAGCircuit = &circuit_to_dag.call1((block?,))?.extract()?; length += inner_dag.size(py, true)?; } @@ -2343,7 +2344,7 @@ def _format(operand): } else { let blocks = inst_bound.getattr("blocks")?; let mut block_weights: Vec = Vec::with_capacity(blocks.len()?); - for block in blocks.iter()? { + for block in blocks.try_iter()? { let inner_dag: &DAGCircuit = &circuit_to_dag.call1((block?,))?.extract()?; block_weights.push(inner_dag.depth(py, true)?); } @@ -2465,7 +2466,7 @@ def _format(operand): .chain(self.clbits.bits()) .enumerate() .map(|(idx, bit)| (bit, idx)); - indices.into_py_dict_bound(py) + indices.into_py_dict(py)? }; let other_bit_indices = { @@ -2476,7 +2477,7 @@ def _format(operand): .chain(other.clbits.bits()) .enumerate() .map(|(idx, bit)| (bit, idx)); - indices.into_py_dict_bound(py) + indices.into_py_dict(py)? }; // Check if qregs are the same. @@ -2696,9 +2697,9 @@ def _format(operand): .collect() }; - Ok(PyTuple::new_bound(py, nodes?) + Ok(PyTuple::new(py, nodes?)? .into_any() - .iter() + .try_iter() .unwrap() .unbind()) } @@ -2735,9 +2736,9 @@ def _format(operand): .collect() }; - Ok(PyTuple::new_bound(py, nodes?) + Ok(PyTuple::new(py, nodes?)? .into_any() - .iter() + .try_iter() .unwrap() .unbind()) } @@ -2895,7 +2896,7 @@ def _format(operand): } let mut qubit_wire_map = HashMap::new(); let mut clbit_wire_map = HashMap::new(); - let var_map = PyDict::new_bound(py); + let var_map = PyDict::new(py); for (index, wire) in wires.iter().enumerate() { if wire.is_instance(imports::QUBIT.get_bound(py))? { if index >= qargs_len { @@ -2932,7 +2933,7 @@ def _format(operand): Ok(bound_wires) => { let mut qubit_wire_map = HashMap::new(); let mut clbit_wire_map = HashMap::new(); - let var_map = PyDict::new_bound(py); + let var_map = PyDict::new(py); for (source_wire, target_wire) in bound_wires.iter() { if source_wire.is_instance(imports::QUBIT.get_bound(py))? { qubit_wire_map.insert( @@ -2978,7 +2979,7 @@ def _format(operand): let node_vars = if self.may_have_additional_wires(py, &node) { let (_additional_clbits, additional_vars) = self.additional_wires(py, node.op.view(), node.condition())?; - let var_set = PySet::new_bound(py, &additional_vars)?; + let var_set = PySet::new(py, &additional_vars)?; if input_dag_var_set .call_method1(intern!(py, "difference"), (var_set.clone(),))? .is_truthy()? @@ -2990,7 +2991,7 @@ def _format(operand): } var_set } else { - PySet::empty_bound(py)? + PySet::empty(py)? }; let bound_var_map = var_map.bind(py); for var in input_dag_var_set.iter() { @@ -3046,7 +3047,7 @@ def _format(operand): // in favour of the new-style conditional blocks. The extra logic in here to add // additional wires into the map as necessary would hugely complicate matters if we tried // to abstract it out into the `VariableMapper` used elsewhere. - let wire_map = PyDict::new_bound(py); + let wire_map = PyDict::new(py); for (source_qubit, target_qubit) in &qubit_wire_map { wire_map.set_item( in_dag.qubits.get(*source_qubit).unwrap().clone_ref(py), @@ -3061,7 +3062,7 @@ def _format(operand): } wire_map.update(var_map.bind(py).as_mapping())?; - let reverse_wire_map = wire_map.iter().map(|(k, v)| (v, k)).into_py_dict_bound(py); + let reverse_wire_map = wire_map.iter().map(|(k, v)| (v, k)).into_py_dict(py)?; let (py_target, py_value): (Bound, Bound) = condition.bind(py).extract()?; let (py_new_target, target_cargs) = @@ -3077,18 +3078,18 @@ def _format(operand): reverse_wire_map.set_item(&py_target, &new_target)?; Ok(new_target) })?; - (new_target.clone(), PySet::new_bound(py, &[new_target])?) + (new_target.clone(), PySet::new(py, &[new_target])?) } else { // ClassicalRegister let target_bits: Vec> = - py_target.iter()?.collect::>()?; + py_target.try_iter()?.collect::>()?; let mapped_bits: Vec>> = target_bits .iter() .map(|b| reverse_wire_map.get_item(b)) .collect::>()?; let mut new_target = Vec::with_capacity(target_bits.len()); - let target_cargs = PySet::empty_bound(py)?; + let target_cargs = PySet::empty(py)?; for (ours, theirs) in target_bits.into_iter().zip(mapped_bits) { if let Some(theirs) = theirs { // Target bit was in node's wires. @@ -3104,14 +3105,14 @@ def _format(operand): target_cargs.add(theirs)?; } } - let kwargs = [("bits", new_target.into_py(py))].into_py_dict_bound(py); + let kwargs = [("bits", new_target.into_pyobject(py)?)].into_py_dict(py)?; let new_target_register = imports::CLASSICAL_REGISTER .get_bound(py) .call((), Some(&kwargs))?; in_dag.add_creg(py, &new_target_register)?; (new_target_register, target_cargs) }; - let new_condition = PyTuple::new_bound(py, [py_new_target, py_value]); + let new_condition = PyTuple::new(py, [py_new_target, py_value])?; qubit_wire_map.clear(); clbit_wire_map.clear(); @@ -3193,7 +3194,7 @@ def _format(operand): }; self.global_phase = add_global_phase(py, &self.global_phase, &input_dag.global_phase)?; - let wire_map_dict = PyDict::new_bound(py); + let wire_map_dict = PyDict::new(py); for (source, target) in clbit_wire_map.iter() { let source_bit = match new_input_dag { Some(ref in_dag) => in_dag.clbits.get(*source), @@ -3208,7 +3209,7 @@ def _format(operand): // measure until qiskit.expr is ported to Rust. It is necessary because we cannot easily // have Python call back to DAGCircuit::add_creg while we're currently borrowing // the DAGCircuit. - let new_registers = PyList::empty_bound(py); + let new_registers = PyList::empty(py); let add_new_register = new_registers.getattr("append")?.unbind(); let flush_new_registers = |dag: &mut DAGCircuit| -> PyResult<()> { for reg in &new_registers { @@ -3236,7 +3237,7 @@ def _format(operand): if old_op.name() == "switch_case" { let raw_target = old_op.instruction.getattr(py, "target")?; let target = raw_target.bind(py); - let kwargs = PyDict::new_bound(py); + let kwargs = PyDict::new(py); kwargs.set_item("label", old_inst.extra_attrs.label())?; let new_op = imports::SWITCH_CASE_OP.get_bound(py).call( ( @@ -3300,7 +3301,7 @@ def _format(operand): } } } - let out_dict = PyDict::new_bound(py); + let out_dict = PyDict::new(py); for (old_index, new_index) in node_map { out_dict.set_item(old_index.index(), self.get_node(py, new_index)?)?; } @@ -3355,7 +3356,7 @@ def _format(operand): { node.instruction.py_op = new_weight.py_op.clone(); } - Ok(node.into_py(py)) + node.into_py_any(py) } else { self.get_node(py, node_index) } @@ -3383,7 +3384,7 @@ def _format(operand): vars_mode: &str, ) -> PyResult> { let connected_components = rustworkx_core::connectivity::connected_components(&self.dag); - let dags = PyList::empty_bound(py); + let dags = PyList::empty(py); for comp_nodes in connected_components.iter() { let mut new_dag = self.copy_empty_like(py, vars_mode)?; @@ -3490,7 +3491,7 @@ def _format(operand): .filter(|e| e.is_instance(imports::QUBIT.get_bound(py)).unwrap()) .collect(); - let qubits = PyTuple::new_bound(py, idle_wires); + let qubits = PyTuple::new(py, idle_wires)?; new_dag.remove_qubits(py, &qubits)?; // TODO: this does not really work, some issue with remove_qubits itself } @@ -3602,8 +3603,8 @@ def _format(operand): .node_references() .map(|(node, weight)| self.unpack_into(py, node, weight)) .collect(); - let tup = PyTuple::new_bound(py, result?); - Ok(tup.into_any().iter().unwrap().unbind()) + let tup = PyTuple::new(py, result?)?; + Ok(tup.into_any().try_iter().unwrap().unbind()) } /// Iterator for edge values with source and destination node. @@ -3632,7 +3633,7 @@ def _format(operand): if let Ok(node) = get_node_index(&nodes) { out.push(node); } else { - for node in nodes.iter()? { + for node in nodes.try_iter()? { out.push(get_node_index(&node?)?); } } @@ -3655,9 +3656,9 @@ def _format(operand): } } - Ok(PyTuple::new_bound(py, edges) + Ok(PyTuple::new(py, edges)? .into_any() - .iter() + .try_iter() .unwrap() .unbind()) } @@ -3819,9 +3820,9 @@ def _format(operand): .unique() .map(|i| self.get_node(py, i)) .collect(); - Ok(PyTuple::new_bound(py, successors?) + Ok(PyTuple::new(py, successors?)? .into_any() - .iter() + .try_iter() .unwrap() .unbind()) } @@ -3834,9 +3835,9 @@ def _format(operand): .unique() .map(|i| self.get_node(py, i)) .collect(); - Ok(PyTuple::new_bound(py, predecessors?) + Ok(PyTuple::new(py, predecessors?)? .into_any() - .iter() + .try_iter() .unwrap() .unbind()) } @@ -3852,9 +3853,9 @@ def _format(operand): _ => None, }) .collect(); - Ok(PyTuple::new_bound(py, predecessors?) + Ok(PyTuple::new(py, predecessors?)? .into_any() - .iter() + .try_iter() .unwrap() .unbind()) } @@ -3870,9 +3871,9 @@ def _format(operand): _ => None, }) .collect(); - Ok(PyTuple::new_bound(py, predecessors?) + Ok(PyTuple::new(py, predecessors?)? .into_any() - .iter() + .try_iter() .unwrap() .unbind()) } @@ -3899,9 +3900,9 @@ def _format(operand): .quantum_predecessors(node.node.unwrap()) .map(|i| self.get_node(py, i)) .collect(); - Ok(PyTuple::new_bound(py, predecessors?) + Ok(PyTuple::new(py, predecessors?)? .into_any() - .iter() + .try_iter() .unwrap() .unbind()) } @@ -3914,9 +3915,9 @@ def _format(operand): .quantum_successors(node.node.unwrap()) .map(|i| self.get_node(py, i)) .collect(); - Ok(PyTuple::new_bound(py, successors?) + Ok(PyTuple::new(py, successors?)? .into_any() - .iter() + .try_iter() .unwrap() .unbind()) } @@ -3931,9 +3932,9 @@ def _format(operand): }); let predecessors: PyResult> = filtered.unique().map(|i| self.get_node(py, i)).collect(); - Ok(PyTuple::new_bound(py, predecessors?) + Ok(PyTuple::new(py, predecessors?)? .into_any() - .iter() + .try_iter() .unwrap() .unbind()) } @@ -3945,7 +3946,7 @@ def _format(operand): .ancestors(node.node.unwrap()) .map(|node| self.get_node(py, node)) .collect(); - Ok(PySet::new_bound(py, &ancestors?)?.unbind()) + Ok(PySet::new(py, &ancestors?)?.unbind()) } /// Returns set of the descendants of a node as DAGOpNodes and DAGOutNodes. @@ -3955,7 +3956,7 @@ def _format(operand): .descendants(node.node.unwrap()) .map(|node| self.get_node(py, node)) .collect(); - Ok(PySet::new_bound(py, &descendants?)?.unbind()) + Ok(PySet::new(py, &descendants?)?.unbind()) } /// Returns an iterator of tuples of (DAGNode, [DAGNodes]) where the DAGNode is the current node @@ -3974,9 +3975,9 @@ def _format(operand): )) }) .collect(); - Ok(PyList::new_bound(py, successor_index?) + Ok(PyList::new(py, successor_index?)? .into_any() - .iter()? + .try_iter()? .unbind()) } @@ -3990,9 +3991,9 @@ def _format(operand): }); let predecessors: PyResult> = filtered.unique().map(|i| self.get_node(py, i)).collect(); - Ok(PyTuple::new_bound(py, predecessors?) + Ok(PyTuple::new(py, predecessors?)? .into_any() - .iter() + .try_iter() .unwrap() .unbind()) } @@ -4084,7 +4085,7 @@ def _format(operand): #[pyo3(name = "front_layer")] fn py_front_layer(&self, py: Python) -> PyResult> { let native_front_layer = self.front_layer(); - let front_layer_list = PyList::empty_bound(py); + let front_layer_list = PyList::empty(py); for node in native_front_layer { front_layer_list.append(self.get_node(py, node)?)?; } @@ -4109,14 +4110,14 @@ def _format(operand): /// the desired behavior. #[pyo3(signature = (*, vars_mode="captures"))] fn layers(&self, py: Python, vars_mode: &str) -> PyResult> { - let layer_list = PyList::empty_bound(py); + let layer_list = PyList::empty(py); let mut graph_layers = self.multigraph_layers(); if graph_layers.next().is_none() { - return Ok(PyIterator::from_bound_object(&layer_list)?.into()); + return Ok(PyIterator::from_object(&layer_list)?.into()); } for graph_layer in graph_layers { - let layer_dict = PyDict::new_bound(py); + let layer_dict = PyDict::new(py); // Sort to make sure they are in the order they were added to the original DAG // It has to be done by node_id as graph_layer is just a list of nodes // with no implied topology @@ -4134,7 +4135,7 @@ def _format(operand): op_nodes.sort_by_key(|(_, node_index)| **node_index); if op_nodes.is_empty() { - return Ok(PyIterator::from_bound_object(&layer_list)?.into()); + return Ok(PyIterator::from_object(&layer_list)?.into()); } let mut new_layer = self.copy_empty_like(py, vars_mode)?; @@ -4148,22 +4149,23 @@ def _format(operand): } }); let support_iter = new_layer_op_nodes.into_iter().map(|node| { - PyTuple::new_bound( + PyTuple::new( py, new_layer .qubits .map_indices(new_layer.qargs_interner.get(node.qubits)), ) + .unwrap() }); - let support_list = PyList::empty_bound(py); + let support_list = PyList::empty(py); for support_qarg in support_iter { support_list.append(support_qarg)?; } - layer_dict.set_item("graph", new_layer.into_py(py))?; + layer_dict.set_item("graph", new_layer)?; layer_dict.set_item("partition", support_list)?; layer_list.append(layer_dict)?; } - Ok(layer_list.into_any().iter()?.into()) + Ok(layer_list.into_any().try_iter()?.into()) } /// Yield a layer for all gates of this circuit. @@ -4172,7 +4174,7 @@ def _format(operand): /// same structure as in layers(). #[pyo3(signature = (*, vars_mode="captures"))] fn serial_layers(&self, py: Python, vars_mode: &str) -> PyResult> { - let layer_list = PyList::empty_bound(py); + let layer_list = PyList::empty(py); for next_node in self.topological_op_nodes()? { let retrieved_node: &PackedInstruction = match self.dag.node_weight(next_node) { Some(NodeType::Operation(node)) => node, @@ -4181,14 +4183,14 @@ def _format(operand): let mut new_layer = self.copy_empty_like(py, vars_mode)?; // Save the support of the operation we add to the layer - let support_list = PyList::empty_bound(py); - let qubits = PyTuple::new_bound( + let support_list = PyList::empty(py); + let qubits = PyTuple::new( py, self.qargs_interner .get(retrieved_node.qubits) .iter() .map(|qubit| self.qubits.get(*qubit)), - ) + )? .unbind(); new_layer.push_back(py, retrieved_node.clone())?; @@ -4197,14 +4199,14 @@ def _format(operand): } let layer_dict = [ - ("graph", new_layer.into_py(py)), + ("graph", new_layer.into_py_any(py)?), ("partition", support_list.into_any().unbind()), ] - .into_py_dict_bound(py); + .into_py_dict(py)?; layer_list.append(layer_dict)?; } - Ok(layer_list.into_any().iter()?.into()) + Ok(layer_list.into_any().try_iter()?.into()) } /// Yield layers of the multigraph. @@ -4216,9 +4218,8 @@ def _format(operand): .filter_map(|index| self.get_node(py, index).ok()) .collect() }); - let list: Bound = - PyList::new_bound(py, graph_layers.collect::>>()); - Ok(PyIterator::from_bound_object(&list)?.unbind()) + let list: Bound = PyList::new(py, graph_layers.collect::>>())?; + Ok(PyIterator::from_object(&list)?.unbind()) } /// Return a set of non-conditional runs of "op" nodes with the given names. @@ -4238,14 +4239,14 @@ def _format(operand): name_list_set.insert(name.extract::()?); } - let out_set = PySet::empty_bound(py)?; + let out_set = PySet::empty(py)?; for run in self.collect_runs(name_list_set) { - let run_tuple = PyTuple::new_bound( + let run_tuple = PyTuple::new( py, run.into_iter() .map(|node_index| self.get_node(py, node_index).unwrap()), - ); + )?; out_set.add(run_tuple)?; } Ok(out_set.unbind()) @@ -4257,15 +4258,16 @@ def _format(operand): match self.collect_1q_runs() { Some(runs) => { let runs_iter = runs.map(|node_indices| { - PyList::new_bound( + PyList::new( py, node_indices .into_iter() .map(|node_index| self.get_node(py, node_index).unwrap()), ) + .unwrap() .unbind() }); - let out_list = PyList::empty_bound(py); + let out_list = PyList::empty(py); for run_list in runs_iter { out_list.append(run_list)?; } @@ -4283,15 +4285,16 @@ def _format(operand): match self.collect_2q_runs() { Some(runs) => { let runs_iter = runs.into_iter().map(|node_indices| { - PyList::new_bound( + PyList::new( py, node_indices .into_iter() .map(|node_index| self.get_node(py, node_index).unwrap()), ) + .unwrap() .unbind() }); - let out_list = PyList::empty_bound(py); + let out_list = PyList::empty(py); for run_list in runs_iter { out_list.append(run_list)?; } @@ -4340,7 +4343,7 @@ def _format(operand): .into_iter() .map(|n| self.get_node(py, n)) .collect::>>()?; - Ok(PyTuple::new_bound(py, nodes).into_any().iter()?.unbind()) + Ok(PyTuple::new(py, nodes)?.into_any().try_iter()?.unbind()) } /// Count the occurrences of operation names. @@ -4355,7 +4358,7 @@ def _format(operand): /// Mapping[str, int]: a mapping of operation names to the number of times it appears. #[pyo3(name = "count_ops", signature = (*, recurse=true))] fn py_count_ops(&self, py: Python, recurse: bool) -> PyResult { - self.count_ops(py, recurse).map(|x| x.into_py(py)) + self.count_ops(py, recurse)?.into_py_any(py) } /// Count the occurrences of operation names on the longest path. @@ -4475,18 +4478,18 @@ def _format(operand): let qubits_in_cone_vec: Vec<_> = qubits_in_cone.iter().map(|&&qubit| qubit).collect(); let elements = self.qubits.map_indices(&qubits_in_cone_vec[..]); - Ok(PySet::new_bound(py, elements)?.unbind()) + Ok(PySet::new(py, elements)?.unbind()) } /// Return a dictionary of circuit properties. fn properties(&self, py: Python) -> PyResult> { Ok(HashMap::from_iter([ - ("size", self.size(py, false)?.into_py(py)), - ("depth", self.depth(py, false)?.into_py(py)), - ("width", self.width().into_py(py)), - ("qubits", self.num_qubits().into_py(py)), - ("bits", self.num_clbits().into_py(py)), - ("factors", self.num_tensor_factors().into_py(py)), + ("size", self.size(py, false)?.into_py_any(py)?), + ("depth", self.depth(py, false)?.into_py_any(py)?), + ("width", self.width().into_py_any(py)?), + ("qubits", self.num_qubits().into_py_any(py)?), + ("bits", self.num_clbits().into_py_any(py)?), + ("factors", self.num_tensor_factors().into_py_any(py)?), ("operations", self.py_count_ops(py, true)?), ])) } @@ -4518,7 +4521,7 @@ def _format(operand): filename: Option<&str>, style: &str, ) -> PyResult> { - let module = PyModule::import_bound(py, "qiskit.visualization.dag_visualization")?; + let module = PyModule::import(py, "qiskit.visualization.dag_visualization")?; module.call_method1("dag_drawer", (slf, scale, filename, style)) } @@ -4532,7 +4535,7 @@ def _format(operand): ) -> PyResult> { let mut buffer = Vec::::new(); build_dot(py, self, &mut buffer, graph_attrs, node_attrs, edge_attrs)?; - Ok(PyString::new_bound(py, std::str::from_utf8(&buffer)?)) + Ok(PyString::new(py, std::str::from_utf8(&buffer)?)) } /// Add an input variable to the circuit. @@ -4630,7 +4633,7 @@ def _format(operand): .bind(py) .clone() .into_any() - .iter()? + .try_iter()? .unbind()) } @@ -4640,7 +4643,7 @@ def _format(operand): .bind(py) .clone() .into_any() - .iter()? + .try_iter()? .unbind()) } @@ -4650,19 +4653,19 @@ def _format(operand): .bind(py) .clone() .into_any() - .iter()? + .try_iter()? .unbind()) } /// Iterable over all the classical variables tracked by the circuit. fn iter_vars(&self, py: Python) -> PyResult> { - let out_set = PySet::empty_bound(py)?; + let out_set = PySet::empty(py)?; for var_type_set in &self.vars_by_type { for var in var_type_set.bind(py).iter() { out_set.add(var)?; } } - Ok(out_set.into_any().iter()?.unbind()) + Ok(out_set.into_any().try_iter()?.unbind()) } fn _has_edge(&self, source: usize, target: usize) -> bool { @@ -4687,7 +4690,9 @@ def _format(operand): Wire::Var(var) => self.vars.get(*var).unwrap(), }, ) - .into_py(py) + .into_pyobject(py) + .unwrap() + .unbind() }) .collect() } @@ -4705,7 +4710,9 @@ def _format(operand): Wire::Var(var) => self.vars.get(*var).unwrap(), }, ) - .into_py(py) + .into_pyobject(py) + .unwrap() + .unbind() }) .collect() } @@ -4756,15 +4763,15 @@ def _format(operand): Ok(result) } - fn _edges(&self, py: Python) -> Vec { + fn _edges(&self, py: Python) -> PyResult> { self.dag .edge_indices() .map(|index| { let wire = self.dag.edge_weight(index).unwrap(); match wire { - Wire::Qubit(qubit) => self.qubits.get(*qubit).to_object(py), - Wire::Clbit(clbit) => self.clbits.get(*clbit).to_object(py), - Wire::Var(var) => self.vars.get(*var).to_object(py), + Wire::Qubit(qubit) => self.qubits.get(*qubit).into_py_any(py), + Wire::Clbit(clbit) => self.clbits.get(*clbit).into_py_any(py), + Wire::Var(var) => self.vars.get(*var).into_py_any(py), } }) .collect() @@ -5288,13 +5295,17 @@ impl DAGCircuit { let wires_from_expr = |node: &Bound| -> PyResult<(Vec, Vec)> { let mut clbits = Vec::new(); let mut vars = Vec::new(); - for var in imports::ITER_VARS.get_bound(py).call1((node,))?.iter()? { + for var in imports::ITER_VARS + .get_bound(py) + .call1((node,))? + .try_iter()? + { let var = var?; let var_var = var.getattr("var")?; if var_var.is_instance(imports::CLBIT.get_bound(py))? { clbits.push(self.clbits.find(&var_var).unwrap()); } else if var_var.is_instance(imports::CLASSICAL_REGISTER.get_bound(py))? { - for bit in var_var.iter().unwrap() { + for bit in var_var.try_iter().unwrap() { clbits.push(self.clbits.find(&bit?).unwrap()); } } else { @@ -5333,7 +5344,7 @@ impl DAGCircuit { if let OperationRef::Instruction(inst) = op { let op = inst.instruction.bind(py); if inst.control_flow() { - for var in op.call_method0("iter_captured_vars")?.iter()? { + for var in op.call_method0("iter_captured_vars")?.try_iter()? { vars.push(var?.unbind()) } if op.is_instance(imports::SWITCH_CASE_OP.get_bound(py))? { @@ -5341,7 +5352,7 @@ impl DAGCircuit { if target.is_instance(imports::CLBIT.get_bound(py))? { clbits.push(self.clbits.find(&target).unwrap()); } else if target.is_instance(imports::CLASSICAL_REGISTER.get_bound(py))? { - for bit in target.iter()? { + for bit in target.try_iter()? { clbits.push(self.clbits.find(&bit?).unwrap()); } } else { @@ -5476,7 +5487,7 @@ impl DAGCircuit { py, BitLocations { index: (self.qubits.len() - 1), - registers: PyList::empty_bound(py).unbind(), + registers: PyList::empty(py).unbind(), }, )?, )?; @@ -5492,7 +5503,7 @@ impl DAGCircuit { py, BitLocations { index: (self.clbits.len() - 1), - registers: PyList::empty_bound(py).unbind(), + registers: PyList::empty(py).unbind(), }, )?, )?; @@ -5637,16 +5648,14 @@ impl DAGCircuit { DAGOpNode { instruction: CircuitInstruction { operation: packed.op.clone(), - qubits: PyTuple::new_bound(py, self.qubits.map_indices(qubits)) - .unbind(), - clbits: PyTuple::new_bound(py, self.clbits.map_indices(clbits)) - .unbind(), + qubits: PyTuple::new(py, self.qubits.map_indices(qubits))?.unbind(), + clbits: PyTuple::new(py, self.clbits.map_indices(clbits))?.unbind(), params: packed.params_view().iter().cloned().collect(), extra_attrs: packed.extra_attrs.clone(), #[cfg(feature = "cache_pygates")] py_op: packed.py_op.clone(), }, - sort_key: format!("{:?}", self.sort_key(id)).into_py(py), + sort_key: format!("{:?}", self.sort_key(id)).into_py_any(py)?, }, DAGNode { node: Some(id) }, ), @@ -5863,7 +5872,7 @@ impl DAGCircuit { qubit_map.iter().map(|(x, y)| (*y, *x)).collect(); let reverse_clbit_map: HashMap = clbit_map.iter().map(|(x, y)| (*y, *x)).collect(); - let reverse_var_map = PyDict::new_bound(py); + let reverse_var_map = PyDict::new(py); for (k, v) in bound_var_map.iter() { reverse_var_map.set_item(v, k)?; } @@ -6137,11 +6146,11 @@ impl DAGCircuit { Ok(Self { name: None, - metadata: Some(PyDict::new_bound(py).unbind().into()), + metadata: Some(PyDict::new(py).unbind().into()), calibrations: HashMap::default(), dag: StableDiGraph::with_capacity(num_nodes, num_edges), - qregs: PyDict::new_bound(py).unbind(), - cregs: PyDict::new_bound(py).unbind(), + qregs: PyDict::new(py).unbind(), + cregs: PyDict::new(py).unbind(), qargs_interner: Interner::with_capacity(num_qubits), cargs_interner: Interner::with_capacity(num_clbits), qubits: BitData::with_capacity(py, "qubits".to_string(), num_qubits), @@ -6150,8 +6159,8 @@ impl DAGCircuit { global_phase: Param::Float(0.), duration: None, unit: "dt".to_string(), - qubit_locations: PyDict::new_bound(py).unbind(), - clbit_locations: PyDict::new_bound(py).unbind(), + qubit_locations: PyDict::new(py).unbind(), + clbit_locations: PyDict::new(py).unbind(), qubit_io_map: Vec::with_capacity(num_qubits), clbit_io_map: Vec::with_capacity(num_clbits), var_io_map: Vec::with_capacity(num_vars), @@ -6159,9 +6168,9 @@ impl DAGCircuit { control_flow_module: PyControlFlowModule::new(py)?, vars_info: HashMap::with_capacity(num_vars), vars_by_type: [ - PySet::empty_bound(py)?.unbind(), - PySet::empty_bound(py)?.unbind(), - PySet::empty_bound(py)?.unbind(), + PySet::empty(py)?.unbind(), + PySet::empty(py)?.unbind(), + PySet::empty(py)?.unbind(), ], }) } @@ -6341,17 +6350,17 @@ impl DAGCircuit { continue; } } - out_params.push(p.to_object(py)); + out_params.push(p.into_pyobject(py)?.into_any().unbind()); } - PyTuple::new_bound(py, out_params) + PyTuple::new(py, out_params) } - None => PyTuple::empty_bound(py), - }; + None => Ok(PyTuple::empty(py)), + }?; let qargs = self.qargs_interner.get(instruction.qubits); - let qubits = PyTuple::new_bound(py, qargs.iter().map(|x| x.0)); + let qubits = PyTuple::new(py, qargs.iter().map(|x| x.0))?; self.calibrations[instruction.op.name()] .bind(py) - .contains((qubits, params).to_object(py)) + .contains((qubits, params).into_py_any(py)?) } else { Err(DAGCircuitError::new_err("Specified node is not an op node")) } @@ -6393,7 +6402,7 @@ impl DAGCircuit { panic!("control flow op must be an instruction") }; let blocks = inst.instruction.bind(py).getattr("blocks")?; - for block in blocks.iter()? { + for block in blocks.try_iter()? { let inner_dag: &DAGCircuit = &circuit_to_dag.call1((block?,))?.extract()?; inner(py, inner_dag, counts)?; } @@ -6427,7 +6436,7 @@ impl DAGCircuit { let mut clbit_last_nodes: HashMap = HashMap::default(); // TODO: Refactor once Vars are in rust // Dict [ Var: (int, VarWeight)] - let vars_last_nodes: Bound = PyDict::new_bound(py); + let vars_last_nodes: Bound = PyDict::new(py); // Consume into iterator to obtain size hint let iter = iter.into_iter(); @@ -7036,7 +7045,7 @@ type SortKeyType<'a> = (&'a [Qubit], &'a [Clbit]); /// Emit a Python `DeprecationWarning` for pulse-related dependencies. fn emit_pulse_dependency_deprecation(py: Python, msg: &str) { let _ = imports::WARNINGS_WARN.get_bound(py).call1(( - PyString::new_bound( + PyString::new( py, &format!( "The {} is deprecated as of Qiskit 1.3.0. It will be removed in Qiskit 2.0.0. \ @@ -7045,7 +7054,7 @@ fn emit_pulse_dependency_deprecation(py: Python, msg: &str) { msg ), ), - py.get_type_bound::(), + py.get_type::(), 1, )); } diff --git a/crates/circuit/src/dag_node.rs b/crates/circuit/src/dag_node.rs index 2fdfcdcbaef2..563154f616d4 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -21,13 +21,16 @@ use crate::TupleLikeArg; use ahash::AHasher; use approx::relative_eq; +use num_complex::Complex64; use rustworkx_core::petgraph::stable_graph::NodeIndex; use numpy::IntoPyArray; +use numpy::PyArray2; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; -use pyo3::types::{PyString, PyTuple}; -use pyo3::{intern, IntoPy, PyObject, PyResult, ToPyObject}; +use pyo3::types::PyTuple; +use pyo3::IntoPyObjectExt; +use pyo3::{intern, PyObject, PyResult}; /// Parent class for DAGOpNode, DAGInNode, and DAGOutNode. #[pyclass(module = "qiskit._accelerate.circuit", subclass)] @@ -103,7 +106,7 @@ impl DAGNode { } fn __hash__(&self, py: Python) -> PyResult { - self.py_nid().into_py(py).bind(py).hash() + self.py_nid().into_pyobject(py)?.hash() } } @@ -127,9 +130,9 @@ impl DAGOpNode { #[allow(unused_variables)] dag: Option>, ) -> PyResult> { let py_op = op.extract::()?; - let qargs = qargs.map_or_else(|| PyTuple::empty_bound(py), |q| q.value); + let qargs = qargs.map_or_else(|| PyTuple::empty(py), |q| q.value); let sort_key = qargs.str().unwrap().into(); - let cargs = cargs.map_or_else(|| PyTuple::empty_bound(py), |c| c.value); + let cargs = cargs.map_or_else(|| PyTuple::empty(py), |c| c.value); let instruction = CircuitInstruction { operation: py_op.operation, qubits: qargs.unbind(), @@ -249,21 +252,17 @@ impl DAGOpNode { instruction, sort_key, }); - Ok(Py::new(py, sub)?.to_object(py)) + Py::new(py, sub)?.into_py_any(py) } fn __reduce__(slf: PyRef, py: Python) -> PyResult { let state = (slf.as_ref().node.map(|node| node.index()), &slf.sort_key); - Ok(( - py.get_type_bound::(), - ( - slf.instruction.get_operation(py)?, - &slf.instruction.qubits, - &slf.instruction.clbits, - ), - state, - ) - .into_py(py)) + let temp = ( + slf.instruction.get_operation(py)?, + &slf.instruction.qubits, + &slf.instruction.clbits, + ); + (py.get_type::(), temp, state).into_py_any(py) } fn __setstate__(mut slf: PyRefMut, state: &Bound) -> PyResult<()> { @@ -347,13 +346,13 @@ impl DAGOpNode { /// Returns the Instruction name corresponding to the op for this node #[getter] - fn get_name(&self, py: Python) -> Py { - self.instruction.operation.name().into_py(py) + fn get_name(&self) -> &str { + self.instruction.operation.name() } #[getter] - fn get_params(&self, py: Python) -> PyObject { - self.instruction.params.to_object(py) + fn get_params(&self) -> &[Param] { + self.instruction.params.as_slice() } #[setter] @@ -362,9 +361,9 @@ impl DAGOpNode { } #[getter] - fn matrix(&self, py: Python) -> Option { + fn matrix<'py>(&'py self, py: Python<'py>) -> Option>> { let matrix = self.instruction.operation.matrix(&self.instruction.params); - matrix.map(|mat| mat.into_pyarray_bound(py).into()) + matrix.map(|mat| mat.into_pyarray(py)) } #[getter] @@ -491,9 +490,9 @@ impl DAGInNode { )) } - fn __reduce__(slf: PyRef, py: Python) -> PyObject { + fn __reduce__<'py>(slf: PyRef<'py, Self>, py: Python<'py>) -> PyResult> { let state = (slf.as_ref().node.map(|node| node.index()), &slf.sort_key); - (py.get_type_bound::(), (&slf.wire,), state).into_py(py) + (py.get_type::(), (&slf.wire,), state).into_pyobject(py) } fn __setstate__(mut slf: PyRefMut, state: &Bound) -> PyResult<()> { @@ -564,9 +563,9 @@ impl DAGOutNode { )) } - fn __reduce__(slf: PyRef, py: Python) -> PyObject { + fn __reduce__(slf: PyRef, py: Python) -> PyResult { let state = (slf.as_ref().node.map(|node| node.index()), &slf.sort_key); - (py.get_type_bound::(), (&slf.wire,), state).into_py(py) + (py.get_type::(), (&slf.wire,), state).into_py_any(py) } fn __setstate__(mut slf: PyRefMut, state: &Bound) -> PyResult<()> { diff --git a/crates/circuit/src/dot_utils.rs b/crates/circuit/src/dot_utils.rs index c31488e92c81..e969e0287460 100644 --- a/crates/circuit/src/dot_utils.rs +++ b/crates/circuit/src/dot_utils.rs @@ -77,16 +77,22 @@ where static ATTRS_TO_ESCAPE: [&str; 2] = ["label", "tooltip"]; /// Convert an attr map to an output string -fn attr_map_to_string( - py: Python, - attrs: Option<&PyObject>, +fn attr_map_to_string<'py, T: IntoPyObject<'py>>( + py: Python<'py>, + attrs: Option<&'py PyObject>, weight: T, -) -> PyResult { +) -> PyResult +where + >::Output: pyo3::IntoPyObject<'py>, + >::Error: std::fmt::Debug, +{ if attrs.is_none() { return Ok("".to_string()); } let attr_callable = |node: T| -> PyResult> { - let res = attrs.unwrap().call1(py, (node.to_object(py),))?; + let res = attrs + .unwrap() + .call1(py, (node.into_pyobject(py).unwrap(),))?; res.extract(py) }; diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 67ae9f85897f..37d81b0380d7 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -41,7 +41,7 @@ impl ImportOnceCell { #[inline] pub fn get(&self, py: Python) -> &Py { self.cell.get_or_init(py, || { - py.import_bound(self.module) + py.import(self.module) .unwrap() .getattr(self.object) .unwrap() @@ -286,7 +286,7 @@ pub fn get_std_gate_class(py: Python, rs_gate: StandardGate) -> PyResult gate.clone_ref(py), None => { let [py_mod, py_class] = STDGATE_IMPORT_PATHS[rs_gate as usize]; - py.import_bound(py_mod)?.getattr(py_class)?.unbind() + py.import(py_mod)?.getattr(py_class)?.unbind() } }; if populate { diff --git a/crates/circuit/src/interner.rs b/crates/circuit/src/interner.rs index a72efb037afb..b77ecb51fa98 100644 --- a/crates/circuit/src/interner.rs +++ b/crates/circuit/src/interner.rs @@ -206,7 +206,8 @@ mod test { fn default_key_exists() { let mut interner = Interner::<[u32]>::new(); assert_eq!(interner.get_default(), interner.get_default()); - assert_eq!(interner.get(interner.get_default()), &[]); + let res: &[u32] = &[]; + assert_eq!(interner.get(interner.get_default()), res); assert_eq!(interner.insert_owned(Vec::new()), interner.get_default()); assert_eq!(interner.insert(&[]), interner.get_default()); diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index a4064d44b917..392ae681296b 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -87,12 +87,12 @@ impl<'py> FromPyObject<'py> for TupleLikeArg<'py> { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let value = match ob.downcast::() { Ok(seq) => seq.to_tuple()?, - Err(_) => PyTuple::new_bound( + Err(_) => PyTuple::new( ob.py(), - ob.iter()? + ob.try_iter()? .map(|o| Ok(o?.unbind())) .collect::>>()?, - ), + )?, }; Ok(TupleLikeArg { value }) } diff --git a/crates/circuit/src/operations.rs b/crates/circuit/src/operations.rs index 904368e10e20..2f477d170d52 100644 --- a/crates/circuit/src/operations.rs +++ b/crates/circuit/src/operations.rs @@ -25,12 +25,13 @@ use num_complex::Complex64; use smallvec::{smallvec, SmallVec}; use numpy::IntoPyArray; +use numpy::PyArray2; use numpy::PyReadonlyArray2; use pyo3::prelude::*; use pyo3::types::{IntoPyDict, PyFloat, PyIterator, PyList, PyTuple}; -use pyo3::{intern, IntoPy, Python}; +use pyo3::{intern, Python}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, IntoPyObject, IntoPyObjectRef)] pub enum Param { ParameterExpression(PyObject), Float(f64), @@ -72,26 +73,6 @@ impl<'py> FromPyObject<'py> for Param { } } -impl IntoPy for Param { - fn into_py(self, py: Python) -> PyObject { - match &self { - Self::Float(val) => val.to_object(py), - Self::ParameterExpression(val) => val.clone_ref(py), - Self::Obj(val) => val.clone_ref(py), - } - } -} - -impl ToPyObject for Param { - fn to_object(&self, py: Python) -> PyObject { - match self { - Self::Float(val) => val.to_object(py), - Self::ParameterExpression(val) => val.clone_ref(py), - Self::Obj(val) => val.clone_ref(py), - } - } -} - impl Param { /// Get an iterator over any Python-space `Parameter` instances tracked within this `Param`. pub fn iter_parameters<'py>(&self, py: Python<'py>) -> PyResult> { @@ -99,13 +80,13 @@ impl Param { match self { Param::Float(_) => Ok(ParamParameterIter(None)), Param::ParameterExpression(expr) => Ok(ParamParameterIter(Some( - expr.bind(py).getattr(parameters_attr)?.iter()?, + expr.bind(py).getattr(parameters_attr)?.try_iter()?, ))), Param::Obj(obj) => { let obj = obj.bind(py); if obj.is_instance(QUANTUM_CIRCUIT.get_bound(py))? { Ok(ParamParameterIter(Some( - obj.getattr(parameters_attr)?.iter()?, + obj.getattr(parameters_attr)?.try_iter()?, ))) } else { Ok(ParamParameterIter(None)) @@ -343,12 +324,6 @@ unsafe impl ::bytemuck::CheckedBitPattern for StandardGate { } unsafe impl ::bytemuck::NoUninit for StandardGate {} -impl ToPyObject for StandardGate { - fn to_object(&self, py: Python) -> Py { - (*self).into_py(py) - } -} - static STANDARD_GATE_NUM_QUBITS: [u32; STANDARD_GATE_SIZE] = [ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0-9 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 10-19 @@ -445,8 +420,8 @@ impl StandardGate { ) -> PyResult> { let gate_class = get_std_gate_class(py, *self)?; let args = match params.unwrap_or(&[]) { - &[] => PyTuple::empty_bound(py), - params => PyTuple::new_bound(py, params), + &[] => PyTuple::empty(py), + params => PyTuple::new(py, params.iter().map(|x| x.into_pyobject(py).unwrap()))?, }; let (label, unit, duration, condition) = ( extra_attrs.label(), @@ -455,8 +430,8 @@ impl StandardGate { extra_attrs.condition(), ); if label.is_some() || unit.is_some() || duration.is_some() || condition.is_some() { - let kwargs = [("label", label.to_object(py))].into_py_dict_bound(py); - let mut out = gate_class.call_bound(py, args, Some(&kwargs))?; + let kwargs = [("label", label.into_pyobject(py)?)].into_py_dict(py)?; + let mut out = gate_class.call(py, args, Some(&kwargs))?; let mut mutable = false; if let Some(condition) = condition { if !mutable { @@ -480,7 +455,7 @@ impl StandardGate { } Ok(out) } else { - gate_class.call_bound(py, args, None) + gate_class.call(py, args, None) } } @@ -688,9 +663,12 @@ impl StandardGate { } // These pymethods are for testing: - pub fn _to_matrix(&self, py: Python, params: Vec) -> Option { - self.matrix(¶ms) - .map(|x| x.into_pyarray_bound(py).into()) + pub fn _to_matrix<'py>( + &self, + py: Python<'py>, + params: Vec, + ) -> Option>> { + self.matrix(¶ms).map(|x| x.into_pyarray(py)) } pub fn _num_params(&self) -> u32 { @@ -741,8 +719,8 @@ impl StandardGate { } #[staticmethod] - pub fn all_gates(py: Python) -> Bound { - PyList::new_bound( + pub fn all_gates(py: Python) -> PyResult> { + PyList::new( py, (0..STANDARD_GATE_SIZE as u8).map(::bytemuck::checked::cast::<_, Self>), ) diff --git a/crates/circuit/src/parameter_table.rs b/crates/circuit/src/parameter_table.rs index 36a7cea7a9a8..bc51ffae5aca 100644 --- a/crates/circuit/src/parameter_table.rs +++ b/crates/circuit/src/parameter_table.rs @@ -10,7 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -use std::cell::OnceCell; +use std::sync::OnceLock; use hashbrown::hash_map::Entry; use hashbrown::{HashMap, HashSet}; @@ -130,12 +130,12 @@ pub struct ParameterTable { /// calculate this on demand and cache it. /// /// Any method that adds or removes a parameter needs to invalidate this. - order_cache: OnceCell>, + order_cache: OnceLock>, /// Cache of a Python-space list of the parameter objects, in order. We only generate this /// specifically when asked. /// /// Any method that adds or removes a parameter needs to invalidate this. - py_parameters_cache: OnceCell>, + py_parameters_cache: OnceLock>, } impl ParameterTable { @@ -234,13 +234,14 @@ impl ParameterTable { pub fn py_parameters<'py>(&self, py: Python<'py>) -> Bound<'py, PyList> { self.py_parameters_cache .get_or_init(|| { - PyList::new_bound( + PyList::new( py, self.order_cache .get_or_init(|| self.sorted_order()) .iter() .map(|uuid| self.by_uuid[uuid].object.bind(py).clone()), ) + .unwrap() .unbind() }) .bind(py) @@ -249,7 +250,7 @@ impl ParameterTable { /// Get a Python set of all tracked `Parameter` objects. pub fn py_parameters_unsorted<'py>(&self, py: Python<'py>) -> PyResult> { - PySet::new_bound(py, self.by_uuid.values().map(|info| &info.object)) + PySet::new(py, self.by_uuid.values().map(|info| &info.object)) } /// Get the sorted order of the `ParameterTable`. This does not access the cache. @@ -380,8 +381,8 @@ impl ParameterTable { .by_uuid .get(&uuid) .ok_or(ParameterTableError::ParameterNotTracked(uuid))?; - // PyO3's `PySet::new_bound` only accepts iterables of references. - let out = PySet::empty_bound(py)?; + // PyO3's `PySet::new` only accepts iterables of references. + let out = PySet::empty(py)?; for usage in info.uses.iter() { match usage { ParameterUse::GlobalPhase => out.add((py.None(), py.None()))?, diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index d8e59e04e51e..acfd8fde72a6 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -18,7 +18,7 @@ fn add_submodule(m: &Bound, constructor: F, name: &str) -> PyResult where F: FnOnce(&Bound) -> PyResult<()>, { - let new_mod = PyModule::new_bound(m.py(), name)?; + let new_mod = PyModule::new(m.py(), name)?; constructor(&new_mod)?; m.add_submodule(&new_mod) } diff --git a/crates/qasm2/src/bytecode.rs b/crates/qasm2/src/bytecode.rs index df3897aa5b71..6da03887c44e 100644 --- a/crates/qasm2/src/bytecode.rs +++ b/crates/qasm2/src/bytecode.rs @@ -211,89 +211,108 @@ pub enum InternalBytecode { }, } -impl IntoPy for InternalBytecode { +impl<'py> IntoPyObject<'py> for InternalBytecode { + type Target = Bytecode; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; + /// Convert the internal bytecode representation to a Python-space one. - fn into_py(self, py: Python<'_>) -> Bytecode { - match self { - InternalBytecode::Gate { - id, - arguments, - qubits, - } => Bytecode { - opcode: OpCode::Gate, - operands: (id, arguments, qubits).into_py(py), - }, - InternalBytecode::ConditionedGate { - id, - arguments, - qubits, - creg, - value, - } => Bytecode { - opcode: OpCode::ConditionedGate, - operands: (id, arguments, qubits, creg, value).into_py(py), - }, - InternalBytecode::Measure { qubit, clbit } => Bytecode { - opcode: OpCode::Measure, - operands: (qubit, clbit).into_py(py), - }, - InternalBytecode::ConditionedMeasure { - qubit, - clbit, - creg, - value, - } => Bytecode { - opcode: OpCode::ConditionedMeasure, - operands: (qubit, clbit, creg, value).into_py(py), - }, - InternalBytecode::Reset { qubit } => Bytecode { - opcode: OpCode::Reset, - operands: (qubit,).into_py(py), - }, - InternalBytecode::ConditionedReset { qubit, creg, value } => Bytecode { - opcode: OpCode::ConditionedReset, - operands: (qubit, creg, value).into_py(py), - }, - InternalBytecode::Barrier { qubits } => Bytecode { - opcode: OpCode::Barrier, - operands: (qubits,).into_py(py), - }, - InternalBytecode::DeclareQreg { name, size } => Bytecode { - opcode: OpCode::DeclareQreg, - operands: (name, size).into_py(py), + fn into_pyobject(self, py: Python<'py>) -> Result { + Bound::new( + py, + match self { + InternalBytecode::Gate { + id, + arguments, + qubits, + } => Bytecode { + opcode: OpCode::Gate, + operands: (id, arguments, qubits) + .into_pyobject(py)? + .into_any() + .unbind(), + }, + InternalBytecode::ConditionedGate { + id, + arguments, + qubits, + creg, + value, + } => Bytecode { + opcode: OpCode::ConditionedGate, + operands: (id, arguments, qubits, creg, value) + .into_pyobject(py)? + .into_any() + .unbind(), + }, + InternalBytecode::Measure { qubit, clbit } => Bytecode { + opcode: OpCode::Measure, + operands: (qubit, clbit).into_pyobject(py)?.into_any().unbind(), + }, + InternalBytecode::ConditionedMeasure { + qubit, + clbit, + creg, + value, + } => Bytecode { + opcode: OpCode::ConditionedMeasure, + operands: (qubit, clbit, creg, value) + .into_pyobject(py)? + .into_any() + .unbind(), + }, + InternalBytecode::Reset { qubit } => Bytecode { + opcode: OpCode::Reset, + operands: (qubit,).into_pyobject(py)?.into_any().unbind(), + }, + InternalBytecode::ConditionedReset { qubit, creg, value } => Bytecode { + opcode: OpCode::ConditionedReset, + operands: (qubit, creg, value).into_pyobject(py)?.into_any().unbind(), + }, + InternalBytecode::Barrier { qubits } => Bytecode { + opcode: OpCode::Barrier, + operands: (qubits,).into_pyobject(py)?.into_any().unbind(), + }, + InternalBytecode::DeclareQreg { name, size } => Bytecode { + opcode: OpCode::DeclareQreg, + operands: (name, size).into_pyobject(py)?.into_any().unbind(), + }, + InternalBytecode::DeclareCreg { name, size } => Bytecode { + opcode: OpCode::DeclareCreg, + operands: (name, size).into_pyobject(py)?.into_any().unbind(), + }, + InternalBytecode::DeclareGate { name, num_qubits } => Bytecode { + opcode: OpCode::DeclareGate, + operands: (name, num_qubits).into_pyobject(py)?.into_any().unbind(), + }, + InternalBytecode::GateInBody { + id, + arguments, + qubits, + } => Bytecode { + // In Python space, we don't have to be worried about the types of the + // parameters changing here, so we can just use `OpCode::Gate` unlike in the + // internal bytecode. + opcode: OpCode::Gate, + operands: (id, arguments.into_pyobject(py)?, qubits) + .into_pyobject(py)? + .into_any() + .unbind(), + }, + InternalBytecode::EndDeclareGate {} => Bytecode { + opcode: OpCode::EndDeclareGate, + operands: ().into_pyobject(py)?.into_any().unbind(), + }, + InternalBytecode::DeclareOpaque { name, num_qubits } => Bytecode { + opcode: OpCode::DeclareOpaque, + operands: (name, num_qubits).into_pyobject(py)?.into_any().unbind(), + }, + InternalBytecode::SpecialInclude { indices } => Bytecode { + opcode: OpCode::SpecialInclude, + operands: (indices,).into_pyobject(py)?.into_any().unbind(), + }, }, - InternalBytecode::DeclareCreg { name, size } => Bytecode { - opcode: OpCode::DeclareCreg, - operands: (name, size).into_py(py), - }, - InternalBytecode::DeclareGate { name, num_qubits } => Bytecode { - opcode: OpCode::DeclareGate, - operands: (name, num_qubits).into_py(py), - }, - InternalBytecode::GateInBody { - id, - arguments, - qubits, - } => Bytecode { - // In Python space, we don't have to be worried about the types of the - // parameters changing here, so we can just use `OpCode::Gate` unlike in the - // internal bytecode. - opcode: OpCode::Gate, - operands: (id, arguments.into_py(py), qubits).into_py(py), - }, - InternalBytecode::EndDeclareGate {} => Bytecode { - opcode: OpCode::EndDeclareGate, - operands: ().into_py(py), - }, - InternalBytecode::DeclareOpaque { name, num_qubits } => Bytecode { - opcode: OpCode::DeclareOpaque, - operands: (name, num_qubits).into_py(py), - }, - InternalBytecode::SpecialInclude { indices } => Bytecode { - opcode: OpCode::SpecialInclude, - operands: (indices,).into_py(py), - }, - } + ) } } @@ -348,7 +367,9 @@ impl BytecodeIterator { self.buffer_used += 1; Ok(self.buffer[self.buffer_used - 1] .take() - .map(|bytecode| bytecode.into_py(py))) + .map(|bytecode| bytecode.into_pyobject(py)) + .transpose()? + .map(|x| x.get().clone())) } } } diff --git a/crates/qasm2/src/expr.rs b/crates/qasm2/src/expr.rs index 0186253b2f84..a8ef33d2bd59 100644 --- a/crates/qasm2/src/expr.rs +++ b/crates/qasm2/src/expr.rs @@ -146,57 +146,76 @@ pub enum Expr { CustomFunction(PyObject, Vec), } -impl IntoPy for Expr { - fn into_py(self, py: Python<'_>) -> PyObject { - match self { - Expr::Constant(value) => bytecode::ExprConstant { value }.into_py(py), - Expr::Parameter(index) => bytecode::ExprArgument { index }.into_py(py), +impl<'py> IntoPyObject<'py> for Expr { + type Target = PyAny; // the Python type + type Output = Bound<'py, Self::Target>; // in most cases this will be `Bound` + type Error = PyErr; + + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(match self { + Expr::Constant(value) => bytecode::ExprConstant { value } + .into_pyobject(py)? + .into_any(), + Expr::Parameter(index) => bytecode::ExprArgument { index } + .into_pyobject(py)? + .into_any(), Expr::Negate(expr) => bytecode::ExprUnary { opcode: bytecode::UnaryOpCode::Negate, - argument: expr.into_py(py), + argument: expr.into_pyobject(py)?.unbind(), } - .into_py(py), + .into_pyobject(py)? + .into_any(), Expr::Add(left, right) => bytecode::ExprBinary { opcode: bytecode::BinaryOpCode::Add, - left: left.into_py(py), - right: right.into_py(py), + left: left.into_pyobject(py)?.unbind(), + right: right.into_pyobject(py)?.unbind(), } - .into_py(py), + .into_pyobject(py)? + .into_any(), Expr::Subtract(left, right) => bytecode::ExprBinary { opcode: bytecode::BinaryOpCode::Subtract, - left: left.into_py(py), - right: right.into_py(py), + left: left.into_pyobject(py)?.unbind(), + right: right.into_pyobject(py)?.unbind(), } - .into_py(py), + .into_pyobject(py)? + .into_any(), Expr::Multiply(left, right) => bytecode::ExprBinary { opcode: bytecode::BinaryOpCode::Multiply, - left: left.into_py(py), - right: right.into_py(py), + left: left.into_pyobject(py)?.unbind(), + right: right.into_pyobject(py)?.unbind(), } - .into_py(py), + .into_pyobject(py)? + .into_any(), Expr::Divide(left, right) => bytecode::ExprBinary { opcode: bytecode::BinaryOpCode::Divide, - left: left.into_py(py), - right: right.into_py(py), + left: left.into_pyobject(py)?.unbind(), + right: right.into_pyobject(py)?.unbind(), } - .into_py(py), + .into_pyobject(py)? + .into_any(), Expr::Power(left, right) => bytecode::ExprBinary { opcode: bytecode::BinaryOpCode::Power, - left: left.into_py(py), - right: right.into_py(py), + left: left.into_pyobject(py)?.unbind(), + right: right.into_pyobject(py)?.unbind(), } - .into_py(py), + .into_pyobject(py)? + .into_any(), Expr::Function(func, expr) => bytecode::ExprUnary { opcode: func.into(), - argument: expr.into_py(py), + argument: expr.into_pyobject(py)?.unbind(), } - .into_py(py), + .into_pyobject(py)? + .into_any(), Expr::CustomFunction(func, exprs) => bytecode::ExprCustom { callable: func, - arguments: exprs.into_iter().map(|expr| expr.into_py(py)).collect(), + arguments: exprs + .into_iter() + .map(|expr| expr.into_pyobject(py).unwrap().unbind()) + .collect(), } - .into_py(py), - } + .into_pyobject(py)? + .into_any(), + }) } } @@ -424,7 +443,7 @@ impl ExprParser<'_> { // 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_bound( + let args = PyTuple::new( py, exprs.iter().map(|x| { if let Expr::Constant(val) = x { @@ -433,7 +452,7 @@ impl ExprParser<'_> { unreachable!() } }), - ); + )?; match callable.call1(py, args) { Ok(retval) => { match retval.extract::(py) { diff --git a/crates/qasm2/src/lex.rs b/crates/qasm2/src/lex.rs index cfac9e98fce0..fc92e4e6c860 100644 --- a/crates/qasm2/src/lex.rs +++ b/crates/qasm2/src/lex.rs @@ -337,7 +337,7 @@ pub struct TokenStream { /// backing file or other named resource. pub filename: std::ffi::OsString, strict: bool, - source: Box, + source: Box, line_buffer: Vec, done: bool, line: usize, @@ -352,7 +352,7 @@ impl TokenStream { /// Create and initialise a generic [TokenStream], given a source that implements /// [std::io::BufRead] and a filename (or resource path) that describes its source. fn new( - source: Box, + source: Box, filename: std::ffi::OsString, strict: bool, ) -> Self { diff --git a/crates/qasm2/src/parse.rs b/crates/qasm2/src/parse.rs index ec8c61152bab..1d0b150b0a19 100644 --- a/crates/qasm2/src/parse.rs +++ b/crates/qasm2/src/parse.rs @@ -17,7 +17,7 @@ use hashbrown::{HashMap, HashSet}; use num_bigint::BigUint; -use pyo3::prelude::{PyObject, PyResult, Python}; +use pyo3::prelude::{IntoPyObject, PyObject, PyResult}; use crate::bytecode::InternalBytecode; use crate::error::{ @@ -64,7 +64,7 @@ const BUILTIN_CLASSICAL: [&str; 6] = ["cos", "exp", "ln", "sin", "sqrt", "tan"]; /// the second is whether to also define addition to make offsetting the newtype easier. macro_rules! newtype_id { ($id:ident, false) => { - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPyObject)] pub struct $id(usize); impl $id { @@ -72,12 +72,6 @@ macro_rules! newtype_id { Self(value) } } - - impl pyo3::IntoPy for $id { - fn into_py(self, py: Python<'_>) -> PyObject { - self.0.into_py(py) - } - } }; ($id:ident, true) => { diff --git a/crates/qasm3/src/build.rs b/crates/qasm3/src/build.rs index 912066df839e..37659187df3e 100644 --- a/crates/qasm3/src/build.rs +++ b/crates/qasm3/src/build.rs @@ -11,7 +11,7 @@ // that they have been altered from the originals. use pyo3::prelude::*; -use pyo3::types::{PySequence, PyString, PyTuple}; +use pyo3::types::{PySequence, PyTuple}; use ahash::RandomState; @@ -145,7 +145,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_bound( + let params = PyTuple::new( py, call.params() .as_ref() @@ -154,7 +154,7 @@ impl BuilderState { .iter() .map(|param| expr::eval_gate_param(py, &self.symbols, ast_symbols, param)) .collect::>>()?, - ); + )?; let qargs = call.qubits(); if params.len() != gate.num_params() { return Err(QASM3ImporterError::new_err(format!( @@ -209,7 +209,7 @@ impl BuilderState { } } } - PyTuple::new_bound(py, qubits.values()) + PyTuple::new(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 @@ -320,9 +320,9 @@ impl BuilderState { } } - fn add_qreg>>( - &mut self, - py: Python, + fn add_qreg<'a, T: IntoPyObject<'a>>( + &'a mut self, + py: Python<'a>, ast_symbol: SymbolId, name: T, size: usize, @@ -338,9 +338,9 @@ impl BuilderState { } } - fn add_creg>>( + fn add_creg<'py, T: IntoPyObject<'py>>( &mut self, - py: Python, + py: Python<'py>, ast_symbol: SymbolId, name: T, size: usize, diff --git a/crates/qasm3/src/circuit.rs b/crates/qasm3/src/circuit.rs index 8b46e2bdaf96..5e77b2a8c380 100644 --- a/crates/qasm3/src/circuit.rs +++ b/crates/qasm3/src/circuit.rs @@ -11,7 +11,8 @@ // that they have been altered from the originals. use pyo3::prelude::*; -use pyo3::types::{PyList, PyString, PyTuple, PyType}; +use pyo3::types::{PyAny, PyList, PyString, PyTuple, PyType}; +use pyo3::IntoPyObjectExt; use crate::error::QASM3ImporterError; @@ -27,6 +28,7 @@ pub trait PyRegister { macro_rules! register_type { ($name: ident) => { /// Rust-space wrapper around Qiskit `Register` objects. + #[derive(Clone)] pub struct $name { /// The actual register instance. object: Py, @@ -43,18 +45,13 @@ macro_rules! register_type { } } - impl ::pyo3::IntoPy> for $name { - fn into_py(self, _py: Python) -> Py { - self.object - } - } + impl<'py> IntoPyObject<'py> for $name { + type Target = PyAny; + type Output = Bound<'py, Self::Target>; + type Error = PyErr; - impl ::pyo3::ToPyObject for $name { - fn to_object(&self, py: Python) -> Py { - // _Technically_, allowing access this internal object can let the Rust-space - // wrapper get out-of-sync since we keep a direct handle to the list, but in - // practice, the field it's viewing is private and "inaccessible" from Python. - self.object.clone_ref(py) + fn into_pyobject(self, py: Python<'py>) -> Result { + Ok(self.object.bind(py).clone()) } } }; @@ -86,15 +83,22 @@ pub struct PyGate { } impl PyGate { - pub fn new>, S: AsRef>( - py: Python, + pub fn new< + 'py, + T: IntoPyObject<'py, Target = PyAny, Output = Bound<'py, PyAny>>, + S: AsRef, + >( + py: Python<'py>, constructor: T, name: S, num_params: usize, num_qubits: usize, - ) -> Self { + ) -> Self + where + >::Error: std::fmt::Debug, + { Self { - constructor: constructor.into_py(py), + constructor: constructor.into_pyobject(py).unwrap().unbind(), name: name.as_ref().to_owned(), num_params, num_qubits, @@ -102,14 +106,14 @@ impl PyGate { } /// Construct a Python-space instance of the custom gate. - pub fn construct(&self, py: Python, args: A) -> PyResult> + pub fn construct<'py, A>(&'py self, py: Python<'py>, args: A) -> PyResult> where - A: IntoPy>, + A: pyo3::IntoPyObject<'py, Target = PyTuple, Output = Bound<'py, PyTuple>>, { - let args = args.into_py(py); - let received_num_params = args.bind(py).len(); + let args = args.into_pyobject_or_pyerr(py)?; + let received_num_params = args.len(); if received_num_params == self.num_params { - self.constructor.call1(py, args.bind(py)) + self.constructor.call1(py, args) } else { Err(QASM3ImporterError::new_err(format!( "internal error: wrong number of params for {} (got {}, expected {})", @@ -145,19 +149,19 @@ impl PyGate { } fn __repr__<'py>(&self, py: Python<'py>) -> PyResult> { - PyString::new_bound(py, "CustomGate(name={!r}, num_params={}, num_qubits={})").call_method1( + PyString::new(py, "CustomGate(name={!r}, num_params={}, num_qubits={})").call_method1( "format", ( - PyString::new_bound(py, &self.name), + PyString::new(py, &self.name), self.num_params, self.num_qubits, ), ) } - fn __reduce__(&self, py: Python) -> Py { + fn __reduce__(&self, py: Python) -> PyResult> { ( - PyType::new_bound::(py), + PyType::new::(py), ( self.constructor.clone_ref(py), &self.name, @@ -165,7 +169,8 @@ impl PyGate { self.num_qubits, ), ) - .into_py(py) + .into_pyobject(py) + .map(|x| x.unbind()) } } @@ -189,7 +194,7 @@ pub struct PyCircuitModule { impl PyCircuitModule { /// Import the necessary components from `qiskit.circuit`. pub fn import(py: Python) -> PyResult { - let module = PyModule::import_bound(py, "qiskit.circuit")?; + let module = PyModule::import(py, "qiskit.circuit")?; Ok(Self { circuit: module .getattr("QuantumCircuit")? @@ -214,7 +219,7 @@ impl PyCircuitModule { .downcast_into::()? .unbind(), // Measure is a singleton, so just store the object. - measure: module.getattr("Measure")?.call0()?.into_py(py), + measure: module.getattr("Measure")?.call0()?.into_py_any(py)?, }) } @@ -222,13 +227,13 @@ impl PyCircuitModule { self.circuit.call0(py).map(PyCircuit) } - pub fn new_qreg>>( - &self, - py: Python, + pub fn new_qreg<'a, T: IntoPyObject<'a>>( + &'a self, + py: Python<'a>, name: T, size: usize, ) -> PyResult { - let qreg = self.qreg.call1(py, (size, name.into_py(py)))?; + let qreg = self.qreg.call1(py, (size, name))?; Ok(PyQuantumRegister { items: qreg .bind(py) @@ -243,13 +248,13 @@ impl PyCircuitModule { self.qubit.call0(py) } - pub fn new_creg>>( + pub fn new_creg<'py, T: IntoPyObject<'py>>( &self, - py: Python, + py: Python<'py>, name: T, size: usize, ) -> PyResult { - let creg = self.creg.call1(py, (size, name.into_py(py)))?; + let creg = self.creg.call1(py, (size, name))?; Ok(PyClassicalRegister { items: creg .bind(py) @@ -264,24 +269,34 @@ impl PyCircuitModule { self.clbit.call0(py) } - pub fn new_instruction( - &self, - py: Python, + pub fn new_instruction<'a, O, Q, C>( + &'a self, + py: Python<'a>, operation: O, qubits: Q, clbits: C, ) -> PyResult> where - O: IntoPy>, - Q: IntoPy>, - C: IntoPy>, + O: IntoPyObject<'a>, + Q: IntoPyObject<'a>, + C: IntoPyObject<'a>, + >::Output: pyo3::IntoPyObject<'a>, + >::Output: pyo3::IntoPyObject<'a>, { - self.circuit_instruction - .call1(py, (operation, qubits.into_py(py), clbits.into_py(py))) + self.circuit_instruction.call1( + py, + ( + operation, + qubits.into_pyobject_or_pyerr(py)?, + clbits.into_pyobject_or_pyerr(py)?, + ), + ) } pub fn new_barrier(&self, py: Python, num_qubits: usize) -> PyResult> { - self.barrier.call1(py, (num_qubits,)).map(|x| x.into_py(py)) + self.barrier + .call1(py, (num_qubits,)) + .map(|x| x.into_pyobject(py).unwrap().unbind()) } pub fn measure(&self, py: Python) -> Py { @@ -293,6 +308,8 @@ impl PyCircuitModule { /// construct the Python :class:`.QuantumCircuit`. The idea of doing this from Rust space like /// this is that we might steadily be able to move more and more of it into being native Rust as /// the Rust-space APIs around the internal circuit data stabilize. + +#[derive(IntoPyObject, IntoPyObjectRef)] pub struct PyCircuit(Py); impl PyCircuit { @@ -303,7 +320,7 @@ impl PyCircuit { pub fn add_qreg(&self, py: Python, qreg: &PyQuantumRegister) -> PyResult<()> { self.inner(py) - .call_method1("add_register", (qreg.to_object(py),)) + .call_method1("add_register", (qreg.clone().into_pyobject(py)?,)) .map(|_| ()) } @@ -315,25 +332,27 @@ impl PyCircuit { pub fn add_creg(&self, py: Python, creg: &PyClassicalRegister) -> PyResult<()> { self.inner(py) - .call_method1("add_register", (creg.to_object(py),)) + .call_method1("add_register", (creg.clone().into_pyobject(py)?,)) .map(|_| ()) } - pub fn add_clbit>>(&self, py: Python, clbit: T) -> PyResult<()> { + pub fn add_clbit<'a, T: IntoPyObject<'a>>(&'a self, py: Python<'a>, clbit: T) -> PyResult<()> { self.inner(py) .call_method1("add_bits", ((clbit,),)) - .map(|_| ()) + .map(move |_| ()) } - pub fn append>>(&self, py: Python, instruction: T) -> PyResult<()> { + pub fn append<'py, T: IntoPyObject<'py>>( + &'py self, + py: Python<'py>, + instruction: T, + ) -> PyResult<()> + where + >::Output: pyo3::IntoPyObject<'py>, + PyErr: From<>::Error>, + { self.inner(py) - .call_method1("_append", (instruction.into_py(py),)) + .call_method1("_append", (instruction.into_pyobject(py)?,)) .map(|_| ()) } } - -impl ::pyo3::IntoPy> for PyCircuit { - fn into_py(self, py: Python) -> Py { - self.0.clone_ref(py) - } -} diff --git a/crates/qasm3/src/expr.rs b/crates/qasm3/src/expr.rs index 40d2da4af2dc..de39e365b2a5 100644 --- a/crates/qasm3/src/expr.rs +++ b/crates/qasm3/src/expr.rs @@ -12,6 +12,7 @@ use pyo3::prelude::*; use pyo3::types::PyTuple; +use pyo3::IntoPyObjectExt; use hashbrown::HashMap; @@ -122,10 +123,7 @@ impl<'py> Iterator for BroadcastQubitsIter<'py> { BroadcastItem::Register(bits) => bits[offset].clone_ref(self.py), }; self.offset += 1; - Some(PyTuple::new_bound( - self.py, - self.items.iter().map(to_scalar), - )) + Some(PyTuple::new(self.py, self.items.iter().map(to_scalar)).unwrap()) } fn size_hint(&self) -> (usize, Option) { @@ -156,8 +154,8 @@ impl<'py> Iterator for BroadcastMeasureIter<'_, 'py> { }; self.offset += 1; Some(( - PyTuple::new_bound(self.py, &[to_scalar(self.qarg)]), - PyTuple::new_bound(self.py, &[to_scalar(self.carg)]), + PyTuple::new(self.py, &[to_scalar(self.qarg)]).unwrap(), + PyTuple::new(self.py, &[to_scalar(self.carg)]).unwrap(), )) } @@ -177,7 +175,10 @@ fn broadcast_bits_for_identifier( Ok(BroadcastItem::Bit(bit.clone())) } else if let Some(reg) = registers.get(iden_symbol) { Ok(BroadcastItem::Register( - reg.bit_list(py).iter().map(|obj| obj.into_py(py)).collect(), + reg.bit_list(py) + .iter() + .map(|obj| obj.into_py_any(py).unwrap()) + .collect(), )) } else { Err(QASM3ImporterError::new_err(format!( diff --git a/crates/qasm3/src/lib.rs b/crates/qasm3/src/lib.rs index 14ab9525e5bd..2a4f05566835 100644 --- a/crates/qasm3/src/lib.rs +++ b/crates/qasm3/src/lib.rs @@ -62,7 +62,7 @@ pub fn loads( include_path: Option>, ) -> PyResult { let default_include_path = || -> PyResult> { - let filename: PyBackedStr = py.import_bound("qiskit")?.filename()?.try_into()?; + let filename: PyBackedStr = py.import("qiskit")?.filename()?.try_into()?; Ok(vec![Path::new(filename.deref()) .parent() .unwrap() @@ -83,9 +83,9 @@ pub fn loads( .map(|gate| (gate.name().to_owned(), gate)) .collect(), None => py - .import_bound("qiskit.qasm3")? + .import("qiskit.qasm3")? .getattr("STDGATES_INC_GATES")? - .iter()? + .try_iter()? .map(|obj| { let gate = obj?.extract::()?; Ok((gate.name().to_owned(), gate)) @@ -133,21 +133,20 @@ pub fn load( custom_gates: Option>, include_path: Option>, ) -> PyResult { - 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)) - })? - }; + 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)) + })? + }; loads(py, source, custom_gates, include_path) }