From b6e82250f24d5923dc2baedea11753659ea5fb9c Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 4 Dec 2024 20:14:01 -0500 Subject: [PATCH 1/5] Bump PyO3 and rust-numpy to 0.23.x This commit bumps the version of pyo3 and rust-numpy used by qiskit to the latest release 0.23. The largest change by volume of code is the deprecation of all the `*_bound()` methods. These are just warnings but they would be fatal to our CI so it needs to be updated. THe larger functional change that required updating the code is the change in the traits around converting to Python objects. This actually found a bug in the target where we were not returning a proper instruction type for standard gates. This also opens up the opportunity to update our hashbrown version to 0.15.x, but we can't do that until the next rustworkx-core release. --- Cargo.lock | 42 +- Cargo.toml | 4 +- .../basis_translator/compose_transforms.rs | 2 +- .../src/basis/basis_translator/mod.rs | 18 +- crates/accelerate/src/check_map.rs | 2 +- .../accelerate/src/circuit_library/blocks.rs | 8 +- .../src/circuit_library/entanglement.rs | 2 +- .../src/circuit_library/parameter_ledger.rs | 4 +- .../src/circuit_library/pauli_feature_map.rs | 4 +- .../src/circuit_library/quantum_volume.rs | 6 +- crates/accelerate/src/commutation_analysis.rs | 17 +- crates/accelerate/src/commutation_checker.rs | 53 +- crates/accelerate/src/consolidate_blocks.rs | 21 +- crates/accelerate/src/dense_layout.rs | 6 +- crates/accelerate/src/edge_collections.rs | 2 +- crates/accelerate/src/equivalence.rs | 51 +- crates/accelerate/src/error_map.rs | 13 +- .../src/euler_one_qubit_decomposer.rs | 25 +- crates/accelerate/src/gate_direction.rs | 8 +- crates/accelerate/src/isometry.rs | 17 +- crates/accelerate/src/nlayout.rs | 32 +- .../accelerate/src/results/marginalization.rs | 20 +- crates/accelerate/src/sabre/heuristic.rs | 61 ++- crates/accelerate/src/sabre/layout.rs | 4 +- crates/accelerate/src/sabre/mod.rs | 25 +- crates/accelerate/src/sabre/neighbor_table.rs | 15 +- crates/accelerate/src/sabre/route.rs | 7 +- crates/accelerate/src/sparse_observable.rs | 171 ++++--- crates/accelerate/src/sparse_pauli_op.rs | 35 +- crates/accelerate/src/split_2q_unitaries.rs | 2 +- crates/accelerate/src/star_prerouting.rs | 4 +- .../accelerate/src/synthesis/clifford/mod.rs | 2 +- crates/accelerate/src/synthesis/linear/mod.rs | 17 +- crates/accelerate/src/synthesis/mod.rs | 12 +- .../src/synthesis/permutation/mod.rs | 2 +- .../accelerate/src/target_transpiler/mod.rs | 140 +++-- .../target_transpiler/nullable_index_map.rs | 44 +- crates/accelerate/src/twirling.rs | 12 +- crates/accelerate/src/two_qubit_decompose.rs | 75 +-- crates/accelerate/src/uc_gate.rs | 4 +- crates/accelerate/src/unitary_synthesis.rs | 26 +- crates/accelerate/src/utils.rs | 5 +- crates/circuit/src/bit_data.rs | 6 +- crates/circuit/src/circuit_data.rs | 74 ++- crates/circuit/src/circuit_instruction.rs | 87 ++-- crates/circuit/src/converters.rs | 6 +- crates/circuit/src/dag_circuit.rs | 484 ++++++++++-------- crates/circuit/src/dag_node.rs | 42 +- crates/circuit/src/dot_utils.rs | 16 +- crates/circuit/src/imports.rs | 4 +- crates/circuit/src/lib.rs | 10 +- crates/circuit/src/operations.rs | 110 ++-- crates/circuit/src/packed_instruction.rs | 2 +- crates/circuit/src/parameter_table.rs | 15 +- crates/pyext/src/lib.rs | 2 +- crates/qasm2/src/bytecode.rs | 185 ++++--- crates/qasm2/src/expr.rs | 77 +-- crates/qasm2/src/lex.rs | 4 +- crates/qasm2/src/parse.rs | 10 +- crates/qasm3/src/build.rs | 18 +- crates/qasm3/src/circuit.rs | 136 ++--- crates/qasm3/src/expr.rs | 14 +- crates/qasm3/src/lib.rs | 35 +- 63 files changed, 1288 insertions(+), 1069 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2170ee3a9945..9c0390c2c5f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -558,9 +558,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" @@ -581,7 +581,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "rayon", ] @@ -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.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" +checksum = "e484fd2c8b4cb67ab05a318f1fd6fa8f199fcc30819f08f07d200809dba26c15" dependencies = [ "cfg-if", "hashbrown 0.14.5", @@ -1105,9 +1105,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.22.6" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" +checksum = "dc0e0469a84f208e20044b98965e1561028180219e35352a2afaf2b942beff3b" dependencies = [ "once_cell", "target-lexicon", @@ -1115,9 +1115,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.22.6" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" +checksum = "eb1547a7f9966f6f1a0f0227564a9945fe36b90da5a93b3933fc3dc03fae372d" dependencies = [ "libc", "pyo3-build-config", @@ -1125,9 +1125,9 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.22.6" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" +checksum = "fdb6da8ec6fa5cedd1626c886fc8749bdcbb09424a86461eb8cdf096b7c33257" dependencies = [ "proc-macro2", "pyo3-macros-backend", @@ -1137,9 +1137,9 @@ dependencies = [ [[package]] name = "pyo3-macros-backend" -version = "0.22.6" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" +checksum = "38a385202ff5a92791168b1136afae5059d3ac118457bb7bc304c197c2d33e7d" 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 14b2504000a4..75dd8b7fb519 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 fe242c73422f..e237a8638966 100644 --- a/crates/accelerate/src/commutation_checker.rs +++ b/crates/accelerate/src/commutation_checker.rs @@ -21,7 +21,7 @@ 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 qiskit_circuit::bit_data::BitData; use qiskit_circuit::circuit_instruction::{ExtraInstructionAttributes, OperationFromPython}; @@ -169,14 +169,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)?; @@ -208,15 +204,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()) } @@ -709,7 +705,7 @@ impl CommutationLibrary { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, IntoPyObject)] pub enum CommutationLibraryEntry { Commutes(bool), QubitMapping(HashMap; 2]>, bool>), @@ -732,25 +728,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]>), @@ -759,14 +736,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..9645274557d0 100644 --- a/crates/accelerate/src/consolidate_blocks.rs +++ b/crates/accelerate/src/consolidate_blocks.rs @@ -12,8 +12,8 @@ use hashbrown::{HashMap, HashSet}; use ndarray::{aview2, Array2}; -use num_complex::Complex64; -use numpy::{IntoPyArray, PyReadonlyArray2}; +use num_complex::{Complex, Complex64}; +use numpy::{IntoPyArray, PyArray, PyReadonlyArray2}; use pyo3::intern; use pyo3::prelude::*; use rustworkx_core::petgraph::stable_graph::NodeIndex; @@ -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(( + , ndarray::Dim<[usize; 2]>>> as Clone>::clone(&array).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..31558dd8b62a 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 @@ -304,7 +301,7 @@ impl<'py> FromPyObject<'py> for GateOper { /// of [CircuitData]. /// /// [`QuantumCircuit`]: https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.QuantumCircuit -#[derive(Debug, Clone)] +#[derive(Debug, Clone, IntoPyObject)] pub struct CircuitFromPython(pub CircuitData); impl FromPyObject<'_> for CircuitFromPython { @@ -322,22 +319,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 +446,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 +493,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 +513,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 +701,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 +782,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..184562058ddc 100644 --- a/crates/accelerate/src/error_map.rs +++ b/crates/accelerate/src/error_map.rs @@ -101,14 +101,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_pyobject(py)?.into_any().unbind(), 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..82f5e9e12218 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -109,11 +109,17 @@ 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_pyobject(py)? + .into_any() + .unbind()), + 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 +718,11 @@ 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> { + Ok((py.get_type::(), (PyString::new(py, self.as_str()),)) + .into_pyobject(py)? + .into_any() + .unbind()) } #[new] diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index a20dfea00535..95cd0f82736c 100755 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -117,7 +117,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 +389,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 +398,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_pyobject(py)?.into_any().unbind(), }, 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..f86577cb7c50 100644 --- a/crates/accelerate/src/nlayout.rs +++ b/crates/accelerate/src/nlayout.rs @@ -23,7 +23,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 +39,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 +48,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 { @@ -124,11 +115,12 @@ 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).unwrap(),), ) - .into_py(py)) + .into_pyobject(py)? + .into_any() + .unbind()) } /// Return the layout mapping. @@ -143,8 +135,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..4f0f11eef295 100644 --- a/crates/accelerate/src/results/marginalization.rs +++ b/crates/accelerate/src/results/marginalization.rs @@ -132,7 +132,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_pyobject(py).map(|x| x.into_any().unbind()); } let clbit_size = hex_to_bin(first_elem.unwrap()).len(); @@ -154,16 +154,20 @@ pub fn marginal_memory( .iter() .map(|x| BigUint::parse_bytes(x.as_bytes(), 2).unwrap()) .collect::>() - .to_object(py)) + .into_pyobject(py)? + .into_any() + .unbind()) } else { Ok(out_mem .par_iter() .map(|x| BigUint::parse_bytes(x.as_bytes(), 2).unwrap()) .collect::>() - .to_object(py)) + .into_pyobject(py)? + .into_any() + .unbind()) } } else { - Ok(out_mem.to_object(py)) + out_mem.into_pyobject(py).map(|x| x.into_any().unbind()) } } @@ -178,7 +182,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 +196,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 +210,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 +221,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..193d9614a0f1 100644 --- a/crates/accelerate/src/sabre/heuristic.rs +++ b/crates/accelerate/src/sabre/heuristic.rs @@ -34,10 +34,12 @@ impl SetScaling { 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_pyobject(py)? + .into_any() + .unbind()) } } @@ -60,8 +62,11 @@ 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> { + Ok((self.weight, self.scale) + .into_pyobject(py)? + .into_any() + .unbind()) } pub fn __eq__(&self, py: Python, other: Py) -> bool { @@ -74,9 +79,11 @@ impl BasicHeuristic { pub fn __repr__(&self, py: Python) -> PyResult> { let fmt = "BasicHeuristic(weight={!r}, scale={!r})"; - Ok(PyString::new_bound(py, fmt) + Ok(PyString::new(py, fmt) .call_method1("format", (self.weight, self.scale))? - .into_py(py)) + .into_pyobject(py)? + .into_any() + .unbind()) } } @@ -105,8 +112,11 @@ 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> { + Ok((self.weight, self.size, self.scale) + .into_pyobject(py)? + .into_any() + .unbind()) } pub fn __eq__(&self, py: Python, other: Py) -> bool { @@ -119,9 +129,11 @@ impl LookaheadHeuristic { pub fn __repr__(&self, py: Python) -> PyResult> { let fmt = "LookaheadHeuristic(weight={!r}, size={!r}, scale={!r})"; - Ok(PyString::new_bound(py, fmt) + Ok(PyString::new(py, fmt) .call_method1("format", (self.weight, self.size, self.scale))? - .into_py(py)) + .into_pyobject(py)? + .into_any() + .unbind()) } } @@ -145,8 +157,11 @@ 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> { + Ok((self.increment, self.reset) + .into_pyobject(py)? + .into_any() + .unbind()) } pub fn __eq__(&self, py: Python, other: Py) -> bool { @@ -159,9 +174,11 @@ impl DecayHeuristic { pub fn __repr__(&self, py: Python) -> PyResult> { let fmt = "DecayHeuristic(increment={!r}, reset={!r})"; - Ok(PyString::new_bound(py, fmt) + Ok(PyString::new(py, fmt) .call_method1("format", (self.increment, self.reset))? - .into_py(py)) + .into_pyobject(py)? + .into_any() + .unbind()) } } @@ -211,15 +228,17 @@ impl Heuristic { } } - pub fn __getnewargs__(&self, py: Python) -> Py { - ( + pub fn __getnewargs__(&self, py: Python) -> PyResult> { + Ok(( self.basic, self.lookahead, self.decay, self.attempt_limit, self.best_epsilon, ) - .into_py(py) + .into_pyobject(py)? + .into_any() + .unbind()) } /// Set the weight of the ``basic`` heuristic (the sum of distances of gates in the front @@ -268,7 +287,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) + Ok(PyString::new(py, fmt) .call_method1( "format", ( @@ -279,6 +298,8 @@ impl Heuristic { self.best_epsilon, ), )? - .into_py(py)) + .into_pyobject(py)? + .into_any() + .unbind()) } } 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..730c0279d8e2 100644 --- a/crates/accelerate/src/sabre/mod.rs +++ b/crates/accelerate/src/sabre/mod.rs @@ -45,7 +45,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 +70,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_pyobject(py).map(|x| x.into_any().unbind())) + .collect::>>()? + .into_pyarray(py) + .into_any() + .unbind()), None => Err(PyIndexError::new_err(format!( "Node index {object} has no block results", ))), @@ -96,13 +97,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_pyobject(py).map(|x| x.into_any().unbind())) + .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..f182520a2b2f 100644 --- a/crates/accelerate/src/sparse_observable.rs +++ b/crates/accelerate/src/sparse_observable.rs @@ -27,6 +27,7 @@ use pyo3::intern; use pyo3::prelude::*; use pyo3::sync::GILOnceCell; use pyo3::types::{IntoPyDict, PyList, PyType}; +use pyo3::BoundObject; use qiskit_circuit::imports::{ImportOnceCell, NUMPY_COPY_ONLY_IF_NEEDED}; use qiskit_circuit::slice::{PySequenceIndex, SequenceIndex}; @@ -213,14 +214,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 +231,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 +253,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 +1247,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 +1318,21 @@ 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 Ok(self + .term(index) + .to_term() + .into_pyobject(py)? + .into_any() + .unbind()) + } 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 +1363,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 +1397,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 +1407,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 +1420,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 +1449,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 +1460,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 +1472,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 +1537,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 +1585,7 @@ impl SparseObservable { /// Examples: /// /// .. code-block:: python - /// + /// /// >>> SparseObservable.from_label("IIII+ZI") /// /// >>> label = "IYXZI" @@ -1749,7 +1787,7 @@ impl SparseObservable { /// Examples: /// /// .. code-block:: python - /// + /// /// >>> label = "IYXZI" /// >>> pauli = Pauli(label) /// >>> SparseObservable.from_pauli(pauli) @@ -1909,7 +1947,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 +2140,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 +2178,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 +2313,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 +2373,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 +2696,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 +2718,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 +2788,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 +2844,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 +2963,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 +2997,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 +3013,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 +3038,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 +3053,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 +3067,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..bb2a966c6103 100644 --- a/crates/accelerate/src/synthesis/linear/mod.rs +++ b/crates/accelerate/src/synthesis/linear/mod.rs @@ -39,7 +39,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)) + Ok(perm.into_pyobject(py)?.into_any().unbind()) } #[pyfunction] @@ -72,7 +72,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)) + Ok(rank.into_pyobject(py)?.into_any().unbind()) } #[pyfunction] @@ -84,7 +84,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)) + Ok(rank.into_pyobject(py)?.into_any().unbind()) } #[pyfunction] @@ -105,7 +105,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 +126,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 +159,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 +169,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 f57facf28b74..3ad21382b167 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 { @@ -105,7 +88,7 @@ impl TargetOperation { /// Represents a Qiskit `Gate` object, keeps a reference to its Python /// instance for caching purposes. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, IntoPyObject, IntoPyObjectRef)] pub(crate) struct NormalOperation { pub operation: PackedOperation, pub params: SmallVec<[Param; 3]>, @@ -123,18 +106,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 +385,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 +404,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 +439,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 +562,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; @@ -728,9 +712,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 +726,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 +746,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 +795,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 +822,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", @@ -1250,25 +1250,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 29c9da3671bc..9925b5d823e7 100644 --- a/crates/accelerate/src/twirling.rs +++ b/crates/accelerate/src/twirling.rs @@ -322,11 +322,11 @@ fn generate_twirled_circuit( custom_gate_map, optimizer_target, )?; - Ok(new_block.into_py(py)) + Ok(new_block.into_pyobject(py)?.into_any().unbind()) }) .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 +334,7 @@ fn generate_twirled_circuit( .call_method1(intern!(py, "_from_circuit_data"), (block,)) .unwrap() }), - ); + )?; let new_inst_obj = py_inst .instruction @@ -356,8 +356,10 @@ 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_pyobject(py)?.into_any().unbind())) + }) + .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..9cfa0a94e142 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -211,8 +211,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 +229,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 +384,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 +462,10 @@ impl Specialization { Self::fSimabbEquiv => 8, Self::fSimabmbEquiv => 9, }; - Ok((py.get_type_bound::().getattr("_from_u8")?, (val,)).into_py(py)) + Ok((py.get_type::().getattr("_from_u8")?, (val,)) + .into_pyobject(py)? + .into_any() + .unbind()) } #[staticmethod] @@ -1085,15 +1089,15 @@ 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 +1105,9 @@ impl TwoQubitWeylDecomposition { self.requested_fidelity, ), ) - .into_py(py)) + .into_pyobject(py)? + .into_any() + .unbind()) } #[new] @@ -1117,30 +1123,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 +1301,17 @@ 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) => Ok(self.gates[idx] + .clone() + .into_pyobject(py)? + .into_any() + .unbind()), + 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 +1977,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 +2036,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 +2053,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 +2078,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 +2093,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 +2317,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 +2513,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 +2880,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 62f41c78084c..4e9fe92cf289 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)?, @@ -724,7 +724,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],))?; @@ -737,11 +737,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 @@ -773,7 +773,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, ))?; @@ -982,10 +982,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 { @@ -1048,10 +1048,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 3ed429d4f5e0..287b583f7208 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -132,12 +132,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 +160,10 @@ impl CircuitData { self_.global_phase.clone(), ) }; - Ok((ty, args, None::<()>, self_.iter()?).into_py(py)) + Ok((ty, args, None::<()>, self_.try_iter()?) + .into_pyobject(py)? + .into_any() + .unbind()) } /// Returns the current sequence of registered :class:`.Qubit` instances as a list. @@ -296,7 +299,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 +345,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 +356,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 +512,28 @@ 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_pyobject(py) + .unwrap() + .into_any() + .unbind() }; 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 => Ok(PyList::new(py, indices.iter().map(get_single))? + .into_pyobject(py)? + .into_any() + .unbind()), } } @@ -546,7 +559,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 +580,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 +661,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 +712,7 @@ impl CircuitData { } return Ok(()); } - for v in itr.iter()? { + for v in itr.try_iter()? { self.append(v?.downcast()?)?; } Ok(()) @@ -728,7 +741,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 +755,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 +811,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 +862,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, @@ -1320,7 +1337,10 @@ 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_pyobject(py)?.into_any().unbind()), + )?; if new_expr.getattr(parameters_attr)?.len()? == 0 { let out = new_expr.call_method0(numeric_attr)?; if coerce { @@ -1440,10 +1460,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)?, ), )?, )? @@ -1456,7 +1476,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.into_pyobject(py).into(); } for uuid in uuids.iter() { self.param_table.add_use(*uuid, usage)? @@ -1468,7 +1488,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 @@ -1496,7 +1518,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..ef99ccb9de63 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -13,13 +13,14 @@ #[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::{intern, PyObject, PyResult}; +use num_complex::Complex64; use smallvec::SmallVec; use crate::imports::{ @@ -80,7 +81,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 +282,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.into_pyobject(py).into(), }) } @@ -297,7 +298,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 +348,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 +455,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.into_pyobject(py).into(), }) } else { Ok(Self { @@ -475,7 +476,9 @@ impl CircuitInstruction { self.qubits.bind(py), self.clbits.bind(py), ) - .into_py(py)) + .into_pyobject(py)? + .into_any() + .unbind()) } pub fn __repr__(self_: &Bound, py: Python<'_>) -> PyResult { @@ -497,24 +500,36 @@ 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)) + Ok(self + ._legacy_format(py)? + .as_any() + .get_item(key)? + .into_pyobject(py)? + .into_any() + .unbind()) } 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)) + Ok(self + ._legacy_format(py)? + .as_any() + .try_iter()? + .into_pyobject(py)? + .into_any() + .unbind()) } pub fn __len__(&self, py: Python) -> PyResult { @@ -582,15 +597,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 +719,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 +735,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 +750,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 +765,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 +773,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 +800,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..6fb95b059dc7 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -146,13 +146,15 @@ pub enum Wire { } impl Wire { - fn to_pickle(&self, py: Python) -> PyObject { - 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)), + fn to_pickle(&self, py: Python) -> PyResult { + Ok(match self { + Self::Qubit(bit) => (0, bit.0.into_pyobject(py)?), + Self::Clbit(bit) => (1, bit.0.into_pyobject(py)?), + Self::Var(var) => (2, var.0.into_pyobject(py)?), } - .into_py(py) + .into_pyobject(py)? + .into_any() + .unbind()) } fn from_pickle(b: &Bound) -> PyResult { @@ -255,7 +257,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 +284,7 @@ impl PyControlFlowModule { } } +#[derive(IntoPyObject)] struct PyVariableMapper { mapper: Py, } @@ -301,7 +304,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 +316,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 +332,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 +370,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 +383,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 +392,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 +413,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 +432,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 +440,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 +479,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 +517,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 +530,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 +538,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 +546,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 +564,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 +573,12 @@ 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_pyobject(py)? + .into_any() + .unbind(), + ); } out_dict.set_item("nodes", nodes)?; out_dict.set_item( @@ -594,9 +595,11 @@ impl DAGCircuit { ( endpoints.0.index(), endpoints.1.index(), - edge_w.clone().to_pickle(py), + edge_w.clone().to_pickle(py)?, ) - .to_object(py) + .into_pyobject(py)? + .into_any() + .unbind() } None => py.None(), }; @@ -792,7 +795,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 +913,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 +936,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 +996,18 @@ def _format(operand): continue; } } - params.push(p.to_object(py)); + params.push(p.into_pyobject(py)?.into_any().unbind()); } 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 +1082,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 +1115,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 +1148,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 +1233,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 +1241,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 +1365,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 +1441,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 +1449,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 +1573,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 +1907,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 +1927,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 +1953,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 +1972,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 +1999,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 +2038,12 @@ 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_pyobject(py)? + .into_any() + .unbind(), + ), )?; for node in other.topological_nodes()? { @@ -2077,7 +2090,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 +2104,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 +2135,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 +2149,8 @@ def _format(operand): } if !inplace { - Ok(Some(dag.into_py(py))) + let out_obj = dag.into_pyobject(py)?.into_any().unbind(); + Ok(Some(out_obj)) } else { Ok(None) } @@ -2146,7 +2160,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 +2221,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 +2281,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 +2357,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 +2479,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 +2490,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 +2710,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 +2749,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 +2909,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 +2946,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 +2992,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 +3004,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 +3060,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 +3075,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 +3091,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 +3118,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 +3207,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 +3222,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 +3250,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 +3314,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 +3369,7 @@ def _format(operand): { node.instruction.py_op = new_weight.py_op.clone(); } - Ok(node.into_py(py)) + Ok(node.into_pyobject(py)?.into_any().unbind()) } else { self.get_node(py, node_index) } @@ -3383,7 +3397,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 +3504,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 +3616,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 +3646,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 +3669,9 @@ def _format(operand): } } - Ok(PyTuple::new_bound(py, edges) + Ok(PyTuple::new(py, edges)? .into_any() - .iter() + .try_iter() .unwrap() .unbind()) } @@ -3819,9 +3833,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 +3848,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 +3866,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 +3884,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 +3913,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 +3928,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 +3945,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 +3959,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 +3969,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 +3988,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 +4004,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 +4098,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 +4123,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 +4148,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 +4162,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 +4187,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 +4196,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 +4212,14 @@ def _format(operand): } let layer_dict = [ - ("graph", new_layer.into_py(py)), + ("graph", new_layer.into_pyobject(py)?.into_any().unbind()), ("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 +4231,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 +4252,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 +4271,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 +4298,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 +4356,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 +4371,11 @@ 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)) + Ok(self + .count_ops(py, recurse)? + .into_pyobject(py)? + .into_any() + .unbind()) } /// Count the occurrences of operation names on the longest path. @@ -4475,18 +4495,39 @@ 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_pyobject(py)?.into_any().unbind(), + ), + ( + "depth", + self.depth(py, false)? + .into_pyobject(py)? + .into_any() + .unbind(), + ), + ("width", self.width().into_pyobject(py)?.into_any().unbind()), + ( + "qubits", + self.num_qubits().into_pyobject(py)?.into_any().unbind(), + ), + ( + "bits", + self.num_clbits().into_pyobject(py)?.into_any().unbind(), + ), + ( + "factors", + self.num_tensor_factors() + .into_pyobject(py)? + .into_any() + .unbind(), + ), ("operations", self.py_count_ops(py, true)?), ])) } @@ -4518,7 +4559,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 +4573,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 +4671,7 @@ def _format(operand): .bind(py) .clone() .into_any() - .iter()? + .try_iter()? .unbind()) } @@ -4640,7 +4681,7 @@ def _format(operand): .bind(py) .clone() .into_any() - .iter()? + .try_iter()? .unbind()) } @@ -4650,19 +4691,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 +4728,9 @@ def _format(operand): Wire::Var(var) => self.vars.get(*var).unwrap(), }, ) - .into_py(py) + .into_pyobject(py) + .unwrap() + .unbind() }) .collect() } @@ -4705,7 +4748,9 @@ def _format(operand): Wire::Var(var) => self.vars.get(*var).unwrap(), }, ) - .into_py(py) + .into_pyobject(py) + .unwrap() + .unbind() }) .collect() } @@ -4762,9 +4807,27 @@ def _format(operand): .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_pyobject(py) + .unwrap() + .into_any() + .unbind(), + Wire::Clbit(clbit) => self + .clbits + .get(*clbit) + .into_pyobject(py) + .unwrap() + .into_any() + .unbind(), + Wire::Var(var) => self + .vars + .get(*var) + .into_pyobject(py) + .unwrap() + .into_any() + .unbind(), } }) .collect() @@ -5288,13 +5351,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 +5400,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 +5408,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 +5543,7 @@ impl DAGCircuit { py, BitLocations { index: (self.qubits.len() - 1), - registers: PyList::empty_bound(py).unbind(), + registers: PyList::empty(py).unbind(), }, )?, )?; @@ -5492,7 +5559,7 @@ impl DAGCircuit { py, BitLocations { index: (self.clbits.len() - 1), - registers: PyList::empty_bound(py).unbind(), + registers: PyList::empty(py).unbind(), }, )?, )?; @@ -5637,16 +5704,17 @@ 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_pyobject(py)? + .into_any() + .unbind(), }, DAGNode { node: Some(id) }, ), @@ -5863,7 +5931,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 +6205,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 +6218,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 +6227,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 +6409,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_pyobject(py)?.into_any().unbind()) } else { Err(DAGCircuitError::new_err("Specified node is not an op node")) } @@ -6393,7 +6461,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 +6495,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 +7104,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 +7113,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..bb0dc5a30f51 100644 --- a/crates/circuit/src/dag_node.rs +++ b/crates/circuit/src/dag_node.rs @@ -21,13 +21,15 @@ 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::{intern, PyObject, PyResult}; /// Parent class for DAGOpNode, DAGInNode, and DAGOutNode. #[pyclass(module = "qiskit._accelerate.circuit", subclass)] @@ -103,7 +105,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 +129,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,13 +251,13 @@ impl DAGOpNode { instruction, sort_key, }); - Ok(Py::new(py, sub)?.to_object(py)) + Ok(Py::new(py, sub)?.into_pyobject(py)?.into_any().unbind()) } 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::(), + py.get_type::(), ( slf.instruction.get_operation(py)?, &slf.instruction.qubits, @@ -263,7 +265,9 @@ impl DAGOpNode { ), state, ) - .into_py(py)) + .into_pyobject(py)? + .into_any() + .unbind()) } fn __setstate__(mut slf: PyRefMut, state: &Bound) -> PyResult<()> { @@ -347,13 +351,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 +366,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 +495,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<()> { @@ -566,7 +570,11 @@ impl DAGOutNode { fn __reduce__(slf: PyRef, py: Python) -> PyObject { 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) + .unwrap() + .into_any() + .unbind() } 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/lib.rs b/crates/circuit/src/lib.rs index a4064d44b917..1046456721f0 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -33,7 +33,7 @@ use pyo3::prelude::*; use pyo3::types::{PySequence, PyTuple}; pub type BitType = u32; -#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq, FromPyObject)] +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq, FromPyObject, IntoPyObject)] pub struct Qubit(pub BitType); impl Qubit { @@ -56,7 +56,7 @@ impl Qubit { } } -#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq, FromPyObject, IntoPyObject)] pub struct Clbit(pub BitType); impl Clbit { @@ -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 59adfd9e0e8c..1b376abc9e24 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,25 +73,35 @@ 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<'py> IntoPyObject<'py> for Param { +// type Target = PyAny; // the Python type +// type Output = Bound<'py, Self::Target>; // in most cases this will be `Bound` +// type Error = std::convert::Infallible; +// +// fn into_pyobject(self, py: Python<'py>) -> Result { +// Ok(match &self { +// Self::Float(val) => val.into_pyobject(py)?.into_any(), +// Self::ParameterExpression(val) => val.bind(py).clone(), +// Self::Obj(val) => val.bind(py).clone(), +// }) +// } +//} +// +//impl<'py> IntoPyObject<'py> for &Param { +// type Target = PyAny; // the Python type +// type Output = Bound<'py, Self::Target>; // in most cases this will be `Bound` +// type Error = std::convert::Infallible; +// +// fn into_pyobject(self, py: Python<'py>) -> Result { +// Ok(match &self { +// Param::Float(val) => val.into_pyobject(py)?.into_any(), +// Param::ParameterExpression(val) => val.bind(py).clone(), +// Param::Obj(val) => val.bind(py).clone(), +// }) +// } +// +// +//} impl Param { /// Get an iterator over any Python-space `Parameter` instances tracked within this `Param`. @@ -99,13 +110,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)) @@ -334,6 +345,30 @@ pub enum StandardGate { RC3XGate = 51, } +//impl<'py> IntoPyObject<'py> for StandardGate { +// type Target = PyAny; // the Python type +// type Output = Bound<'py, Self::Target>; // in most cases this will be `Bound` +// type Error = std::convert::Infallible; +// +// // Required method +// fn into_pyobject(self, py: Python<'py>) -> Result { +// self.into_pyobject(py) +// } +// +//} +// +//impl<'a, 'py> IntoPyObject<'py> for &'a StandardGate { +// type Target = PyAny; // the Python type +// type Output = Bound<'py, Self::Target>; // in most cases this will be `Bound` +// type Error = std::convert::Infallible; +// +// // Required method +// fn into_pyobject(self, py: Python<'py>) -> Result { +// (*self).into_pyobject(py) +// } +// +//} + unsafe impl ::bytemuck::CheckedBitPattern for StandardGate { type Bits = u8; @@ -343,12 +378,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 @@ -440,8 +469,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(), @@ -450,8 +479,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 { @@ -475,7 +504,7 @@ impl StandardGate { } Ok(out) } else { - gate_class.call_bound(py, args, None) + gate_class.call(py, args, None) } } @@ -683,9 +712,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>( + &'py self, + py: Python<'py>, + params: Vec, + ) -> Option>> { + self.matrix(¶ms).map(|x| x.into_pyarray(py)) } pub fn _num_params(&self) -> u32 { @@ -736,8 +768,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/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs index 4da706280336..9a7121551b9c 100644 --- a/crates/circuit/src/packed_instruction.rs +++ b/crates/circuit/src/packed_instruction.rs @@ -111,7 +111,7 @@ unsafe impl ::bytemuck::NoUninit for PackedOperationType {} /// * The pointed-to data must match the type of the discriminant used to store it. /// * `PackedOperation` must take care to forward implementations of `Clone` and `Drop` to the /// contained pointer. -#[derive(Debug)] +#[derive(Debug, IntoPyObject, IntoPyObjectRef)] #[repr(transparent)] pub struct PackedOperation(usize); 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..03d691ac4d50 100644 --- a/crates/qasm3/src/circuit.rs +++ b/crates/qasm3/src/circuit.rs @@ -11,7 +11,7 @@ // 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 crate::error::QASM3ImporterError; @@ -27,6 +27,7 @@ pub trait PyRegister { macro_rules! register_type { ($name: ident) => { /// Rust-space wrapper around Qiskit `Register` objects. + #[derive(IntoPyObject, Clone)] pub struct $name { /// The actual register instance. object: Py, @@ -42,21 +43,6 @@ macro_rules! register_type { self.items.bind(py) } } - - impl ::pyo3::IntoPy> for $name { - fn into_py(self, _py: Python) -> Py { - self.object - } - } - - 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) - } - } }; } @@ -86,15 +72,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 +95,15 @@ 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>>, + >::Error: std::fmt::Debug, { - let args = args.into_py(py); - let received_num_params = args.bind(py).len(); + let args = args.into_pyobject(py).unwrap(); + 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 +139,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 +159,8 @@ impl PyGate { self.num_qubits, ), ) - .into_py(py) + .into_pyobject(py) + .map(|x| x.unbind()) } } @@ -189,7 +184,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 +209,12 @@ 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_pyobject(py)? + .into_any() + .unbind(), }) } @@ -222,13 +222,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 +243,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 +264,36 @@ 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>, + pyo3::PyErr: From<>::Error>, + pyo3::PyErr: From<>::Error>, { - self.circuit_instruction - .call1(py, (operation, qubits.into_py(py), clbits.into_py(py))) + self.circuit_instruction.call1( + py, + ( + operation, + qubits.into_pyobject(py)?, + clbits.into_pyobject(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 +305,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 +317,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 +329,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..7c315feb1d35 100644 --- a/crates/qasm3/src/expr.rs +++ b/crates/qasm3/src/expr.rs @@ -122,10 +122,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 +153,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 +174,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_pyobject(py).unwrap().into_any().unbind()) + .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) } From 7c11e127a290c35fd8fb5b472a9c43d872202d1e Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 17 Dec 2024 22:11:11 -0500 Subject: [PATCH 2/5] Fix cache pygates build --- crates/circuit/src/circuit_data.rs | 2 +- crates/circuit/src/circuit_instruction.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 8258887b1412..4da41a56a17e 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -1484,7 +1484,7 @@ impl CircuitData { previous.extra_attrs = new_op.extra_attrs; #[cfg(feature = "cache_pygates")] { - previous.py_op = op.into_pyobject(py).into(); + previous.py_op = op.unbind().into(); } for uuid in uuids.iter() { self.param_table.add_use(*uuid, usage)? diff --git a/crates/circuit/src/circuit_instruction.rs b/crates/circuit/src/circuit_instruction.rs index ef99ccb9de63..d34b9bb5857f 100644 --- a/crates/circuit/src/circuit_instruction.rs +++ b/crates/circuit/src/circuit_instruction.rs @@ -282,7 +282,7 @@ impl CircuitInstruction { params: op_parts.params, extra_attrs: op_parts.extra_attrs, #[cfg(feature = "cache_pygates")] - py_op: operation.into_pyobject(py).into(), + py_op: operation.clone().unbind().into(), }) } @@ -455,7 +455,7 @@ impl CircuitInstruction { params: params.unwrap_or(op_parts.params), extra_attrs: op_parts.extra_attrs, #[cfg(feature = "cache_pygates")] - py_op: operation.into_pyobject(py).into(), + py_op: operation.clone().unbind().into(), }) } else { Ok(Self { From 7b9b9d45c0540a6535d9da1fa75d2f7891dba88b Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 18 Dec 2024 12:23:30 -0500 Subject: [PATCH 3/5] Fix cargo test --- crates/circuit/src/interner.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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()); From 34fe4a86bc7a6becc15d7cd61476be44eb00709c Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 18 Dec 2024 12:46:12 -0500 Subject: [PATCH 4/5] Fix impl of IntoPyObject for target's operation --- .../accelerate/src/target_transpiler/mod.rs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 3ad21382b167..4fac83902b89 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -88,13 +88,33 @@ impl TargetOperation { /// Represents a Qiskit `Gate` object, keeps a reference to its Python /// instance for caching purposes. -#[derive(Debug, Clone, IntoPyObject, IntoPyObjectRef)] +#[derive(Debug, Clone)] pub(crate) struct NormalOperation { pub operation: PackedOperation, pub params: SmallVec<[Param; 3]>, 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<'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<'py> FromPyObject<'py> for NormalOperation { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { let operation: OperationFromPython = ob.extract()?; From 422c0910d022afe110eb3677e3b2455f8104c325 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 18 Dec 2024 12:57:14 -0500 Subject: [PATCH 5/5] Fix impl of IntoPyObject for equivalence library circuit --- crates/accelerate/src/equivalence.rs | 15 ++++++++++++++- crates/accelerate/src/target_transpiler/mod.rs | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/accelerate/src/equivalence.rs b/crates/accelerate/src/equivalence.rs index 31558dd8b62a..4e763beb9e34 100644 --- a/crates/accelerate/src/equivalence.rs +++ b/crates/accelerate/src/equivalence.rs @@ -301,9 +301,22 @@ impl<'py> FromPyObject<'py> for GateOper { /// of [CircuitData]. /// /// [`QuantumCircuit`]: https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.QuantumCircuit -#[derive(Debug, Clone, IntoPyObject)] +#[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()))? { diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 4fac83902b89..2109a1a30775 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -643,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; }