From de6bd6f10e1998a408322b79f2e9ca100645324b Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Sun, 8 Dec 2024 17:10:35 -0500 Subject: [PATCH 1/6] Fix: Rebalance `PackedInstruction` to avoid having dead references. - Make all attributes of the `PackedInstruction` private to allow for control of the underlying cached `PyObject` reference stored in Python. This ensures that no dead references are left as soon as the gate is modified from the Rust side. - Reimplement `Clone` trait for `PackedInstruction`s to clear the cached gate whenever we decide make calls to `clone()`. - Re-organize rest of the code to adapt to the new changes. --- .../src/barrier_before_final_measurement.rs | 4 +- .../basis/basis_translator/basis_search.rs | 2 +- .../basis_translator/compose_transforms.rs | 14 +- .../src/basis/basis_translator/mod.rs | 184 ++++---- crates/accelerate/src/check_map.rs | 8 +- crates/accelerate/src/commutation_analysis.rs | 16 +- .../src/commutation_cancellation.rs | 14 +- crates/accelerate/src/consolidate_blocks.rs | 25 +- .../accelerate/src/convert_2q_block_matrix.rs | 8 +- crates/accelerate/src/elide_permutations.rs | 20 +- crates/accelerate/src/equivalence.rs | 2 +- .../src/euler_one_qubit_decomposer.rs | 8 +- crates/accelerate/src/gate_direction.rs | 36 +- crates/accelerate/src/gates_in_basis.rs | 12 +- crates/accelerate/src/inverse_cancellation.rs | 8 +- .../remove_diagonal_gates_before_measure.rs | 4 +- .../accelerate/src/remove_identity_equiv.rs | 10 +- crates/accelerate/src/split_2q_unitaries.rs | 6 +- crates/accelerate/src/twirling.rs | 90 ++-- crates/accelerate/src/unitary_synthesis.rs | 72 ++- crates/circuit/src/circuit_data.rs | 152 +++--- crates/circuit/src/converters.rs | 18 +- crates/circuit/src/dag_circuit.rs | 438 +++++++++--------- crates/circuit/src/packed_instruction.rs | 133 +++++- 24 files changed, 676 insertions(+), 608 deletions(-) diff --git a/crates/accelerate/src/barrier_before_final_measurement.rs b/crates/accelerate/src/barrier_before_final_measurement.rs index b42be53f231c..c3c9ee7809b3 100644 --- a/crates/accelerate/src/barrier_before_final_measurement.rs +++ b/crates/accelerate/src/barrier_before_final_measurement.rs @@ -36,13 +36,13 @@ pub fn barrier_before_final_measurements( let NodeType::Operation(ref inst) = dag.dag()[*node] else { unreachable!(); }; - if !FINAL_OP_NAMES.contains(&inst.op.name()) { + if !FINAL_OP_NAMES.contains(&inst.op().name()) { return false; } let is_final_op = dag.bfs_successors(*node).all(|(_, child_successors)| { !child_successors.iter().any(|suc| match dag.dag()[*suc] { NodeType::Operation(ref suc_inst) => { - !FINAL_OP_NAMES.contains(&suc_inst.op.name()) + !FINAL_OP_NAMES.contains(&suc_inst.op().name()) } _ => false, }) diff --git a/crates/accelerate/src/basis/basis_translator/basis_search.rs b/crates/accelerate/src/basis/basis_translator/basis_search.rs index 4686ba9c4c3f..4a5a608fe1e2 100644 --- a/crates/accelerate/src/basis/basis_translator/basis_search.rs +++ b/crates/accelerate/src/basis/basis_translator/basis_search.rs @@ -102,7 +102,7 @@ pub(crate) fn basis_search( let mut cost_tot = 0; let borrowed_cost = opt_cost_map.borrow(); for instruction in edge_data.rule.circuit.0.iter() { - let instruction_op = instruction.op.view(); + let instruction_op = instruction.op().view(); cost_tot += borrowed_cost[&( instruction_op.name().to_string(), instruction_op.num_qubits(), diff --git a/crates/accelerate/src/basis/basis_translator/compose_transforms.rs b/crates/accelerate/src/basis/basis_translator/compose_transforms.rs index b1366c30bf2f..fa07b224de08 100644 --- a/crates/accelerate/src/basis/basis_translator/compose_transforms.rs +++ b/crates/accelerate/src/basis/basis_translator/compose_transforms.rs @@ -86,7 +86,7 @@ pub(super) fn compose_transforms<'a>( .filter_map(|node| { if let Some(NodeType::Operation(op)) = dag.dag().node_weight(node) { if (gate_name.as_str(), *gate_num_qubits) - == (op.op.name(), op.op.num_qubits()) + == (op.op().name(), op.op().num_qubits()) { Some(( node, @@ -144,11 +144,11 @@ fn get_gates_num_params( for node in dag.op_nodes(true) { if let Some(NodeType::Operation(op)) = dag.dag().node_weight(node) { example_gates.insert( - (op.op.name().to_string(), op.op.num_qubits()), + (op.op().name().to_string(), op.op().num_qubits()), op.params_view().len(), ); - if op.op.control_flow() { - let blocks = op.op.blocks(); + if op.op().control_flow() { + let blocks = op.op().blocks(); for block in blocks { get_gates_num_params_circuit(&block, example_gates)?; } @@ -168,11 +168,11 @@ fn get_gates_num_params_circuit( ) -> PyResult<()> { for inst in circuit.iter() { example_gates.insert( - (inst.op.name().to_string(), inst.op.num_qubits()), + (inst.op().name().to_string(), inst.op().num_qubits()), inst.params_view().len(), ); - if inst.op.control_flow() { - let blocks = inst.op.blocks(); + if inst.op().control_flow() { + let blocks = inst.op().blocks(); for block in blocks { get_gates_num_params_circuit(&block, example_gates)?; } diff --git a/crates/accelerate/src/basis/basis_translator/mod.rs b/crates/accelerate/src/basis/basis_translator/mod.rs index c900f80beff4..f10b0adbdeb4 100644 --- a/crates/accelerate/src/basis/basis_translator/mod.rs +++ b/crates/accelerate/src/basis/basis_translator/mod.rs @@ -215,12 +215,15 @@ fn extract_basis( for node in circuit.op_nodes(true) { let operation: &PackedInstruction = circuit.dag()[node].unwrap_operation(); if !circuit.has_calibration_for_index(py, node)? - && circuit.get_qargs(operation.qubits).len() >= min_qubits + && circuit.get_qargs(operation.qubits()).len() >= min_qubits { - basis.insert((operation.op.name().to_string(), operation.op.num_qubits())); + basis.insert(( + operation.op().name().to_string(), + operation.op().num_qubits(), + )); } - if operation.op.control_flow() { - let OperationRef::Instruction(inst) = operation.op.view() else { + if operation.op().control_flow() { + let OperationRef::Instruction(inst) = operation.op().view() else { unreachable!("Control flow operation is not an instance of PyInstruction.") }; let inst_bound = inst.instruction.bind(py); @@ -248,11 +251,11 @@ fn extract_basis( let has_calibration = circuit .call_method1(intern!(py, "_has_calibration_for"), (&instruction_object,))?; if !has_calibration.is_truthy()? - && circuit_data.get_qargs(inst.qubits).len() >= min_qubits + && circuit_data.get_qargs(inst.qubits()).len() >= min_qubits { - basis.insert((inst.op.name().to_string(), inst.op.num_qubits())); + basis.insert((inst.op().name().to_string(), inst.op().num_qubits())); } - if inst.op.control_flow() { + 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()? { @@ -281,7 +284,7 @@ fn extract_basis_target( ) -> PyResult<()> { for node in dag.op_nodes(true) { let node_obj: &PackedInstruction = dag.dag()[node].unwrap_operation(); - let qargs: &[Qubit] = dag.get_qargs(node_obj.qubits); + let qargs: &[Qubit] = dag.get_qargs(node_obj.qubits()); if dag.has_calibration_for_index(py, node)? || qargs.len() < min_qubits { continue; } @@ -310,17 +313,17 @@ fn extract_basis_target( qargs_local_source_basis .entry(Some(physical_qargs_as_set.into_iter().collect())) .and_modify(|set| { - set.insert((node_obj.op.name().to_string(), node_obj.op.num_qubits())); + set.insert((node_obj.op().name().to_string(), node_obj.op().num_qubits())); }) .or_insert(HashSet::from_iter([( - node_obj.op.name().to_string(), - node_obj.op.num_qubits(), + node_obj.op().name().to_string(), + node_obj.op().num_qubits(), )])); } else { - source_basis.insert((node_obj.op.name().to_string(), node_obj.op.num_qubits())); + source_basis.insert((node_obj.op().name().to_string(), node_obj.op().num_qubits())); } - if node_obj.op.control_flow() { - let OperationRef::Instruction(op) = node_obj.op.view() else { + if node_obj.op().control_flow() { + let OperationRef::Instruction(op) = node_obj.op().view() else { unreachable!("Control flow op is not a control flow op. But control_flow is `true`") }; let bound_inst = op.instruction.bind(py); @@ -356,7 +359,7 @@ fn extract_basis_target_circ( let circ_data_bound = circuit.getattr("_data")?.downcast_into::()?; let circ_data = circ_data_bound.borrow(); for (index, node_obj) in circ_data.iter().enumerate() { - let qargs = circ_data.get_qargs(node_obj.qubits); + let qargs = circ_data.get_qargs(node_obj.qubits()); if circuit .call_method1("_has_calibration_for", (circuit.get_item(index)?,))? .is_truthy()? @@ -389,17 +392,17 @@ fn extract_basis_target_circ( qargs_local_source_basis .entry(Some(physical_qargs_as_set.into_iter().collect())) .and_modify(|set| { - set.insert((node_obj.op.name().to_string(), node_obj.op.num_qubits())); + set.insert((node_obj.op().name().to_string(), node_obj.op().num_qubits())); }) .or_insert(HashSet::from_iter([( - node_obj.op.name().to_string(), - node_obj.op.num_qubits(), + node_obj.op().name().to_string(), + node_obj.op().num_qubits(), )])); } else { - source_basis.insert((node_obj.op.name().to_string(), node_obj.op.num_qubits())); + source_basis.insert((node_obj.op().name().to_string(), node_obj.op().num_qubits())); } - if node_obj.op.control_flow() { - let OperationRef::Instruction(op) = node_obj.op.view() else { + if node_obj.op().control_flow() { + let OperationRef::Instruction(op) = node_obj.op().view() else { unreachable!("Control flow op is not a control flow op. But control_flow is `true`") }; let bound_inst = op.instruction.bind(py); @@ -431,14 +434,14 @@ fn apply_translation( let mut out_dag = dag.copy_empty_like(py, "alike")?; for node in dag.topological_op_nodes()? { let node_obj = dag.dag()[node].unwrap_operation(); - let node_qarg = dag.get_qargs(node_obj.qubits); - let node_carg = dag.get_cargs(node_obj.clbits); + let node_qarg = dag.get_qargs(node_obj.qubits()); + let node_carg = dag.get_cargs(node_obj.clbits()); let qubit_set: HashSet = HashSet::from_iter(node_qarg.iter().copied()); let mut new_op: Option = None; - if target_basis.contains(node_obj.op.name()) || node_qarg.len() < min_qubits { - if node_obj.op.control_flow() { - let OperationRef::Instruction(control_op) = node_obj.op.view() else { - unreachable!("This instruction {} says it is of control flow type, but is not an Instruction instance", node_obj.op.name()) + if target_basis.contains(node_obj.op().name()) || node_qarg.len() < min_qubits { + if node_obj.op().control_flow() { + let OperationRef::Instruction(control_op) = node_obj.op().view() else { + unreachable!("This instruction {} says it is of control flow type, but is not an Instruction instance", node_obj.op().name()) }; let mut flow_blocks = vec![]; let bound_obj = control_op.instruction.bind(py); @@ -476,11 +479,7 @@ fn apply_translation( new_op.operation, node_qarg, node_carg, - if new_op.params.is_empty() { - None - } else { - Some(new_op.params) - }, + (!new_op.params.is_empty()).then_some(new_op.params), new_op.extra_attrs, #[cfg(feature = "cache_pygates")] None, @@ -488,21 +487,17 @@ fn apply_translation( } else { out_dag.apply_operation_back( py, - node_obj.op.clone(), + node_obj.op().clone(), node_qarg, node_carg, - if node_obj.params_view().is_empty() { - None - } else { - Some( - node_obj - .params_view() - .iter() - .map(|param| param.clone_ref(py)) - .collect(), - ) - }, - node_obj.extra_attrs.clone(), + (!node_obj.params_view().is_empty()).then_some( + node_obj + .params_view() + .iter() + .map(|param| param.clone_ref(py)) + .collect(), + ), + node_obj.extra_attrs().clone(), #[cfg(feature = "cache_pygates")] None, )?; @@ -512,25 +507,22 @@ fn apply_translation( let node_qarg_as_physical: Option = Some(node_qarg.iter().map(|x| PhysicalQubit(x.0)).collect()); if qargs_with_non_global_operation.contains_key(&node_qarg_as_physical) - && qargs_with_non_global_operation[&node_qarg_as_physical].contains(node_obj.op.name()) + && qargs_with_non_global_operation[&node_qarg_as_physical] + .contains(node_obj.op().name()) { out_dag.apply_operation_back( py, - node_obj.op.clone(), + node_obj.op().clone(), node_qarg, node_carg, - if node_obj.params_view().is_empty() { - None - } else { - Some( - node_obj - .params_view() - .iter() - .map(|param| param.clone_ref(py)) - .collect(), - ) - }, - node_obj.extra_attrs.clone(), + (!node_obj.params_view().is_empty()).then_some( + node_obj + .params_view() + .iter() + .map(|param| param.clone_ref(py)) + .collect(), + ), + node_obj.extra_attrs().clone(), #[cfg(feature = "cache_pygates")] None, )?; @@ -540,21 +532,17 @@ fn apply_translation( if dag.has_calibration_for_index(py, node)? { out_dag.apply_operation_back( py, - node_obj.op.clone(), + node_obj.op().clone(), node_qarg, node_carg, - if node_obj.params_view().is_empty() { - None - } else { - Some( - node_obj - .params_view() - .iter() - .map(|param| param.clone_ref(py)) - .collect(), - ) - }, - node_obj.extra_attrs.clone(), + (!node_obj.params_view().is_empty()).then_some( + node_obj + .params_view() + .iter() + .map(|param| param.clone_ref(py)) + .collect(), + ), + node_obj.extra_attrs().clone(), #[cfg(feature = "cache_pygates")] None, )?; @@ -573,13 +561,13 @@ fn apply_translation( &extra_inst_map[&unique_qargs], )?; } else if instr_map - .contains_key(&(node_obj.op.name().to_string(), node_obj.op.num_qubits())) + .contains_key(&(node_obj.op().name().to_string(), node_obj.op().num_qubits())) { replace_node(py, &mut out_dag, node_obj.clone(), instr_map)?; } else { return Err(TranspilerError::new_err(format!( "BasisTranslator did not map {}", - node_obj.op.name() + node_obj.op().name() ))); } is_updated = true; @@ -595,13 +583,13 @@ fn replace_node( instr_map: &HashMap, DAGCircuit)>, ) -> PyResult<()> { let (target_params, target_dag) = - &instr_map[&(node.op.name().to_string(), node.op.num_qubits())]; + &instr_map[&(node.op().name().to_string(), node.op().num_qubits())]; if node.params_view().len() != target_params.len() { return Err(TranspilerError::new_err(format!( "Translation num_params not equal to op num_params. \ Op: {:?} {} Translation: {:?}\n{:?}", node.params_view(), - node.op.name(), + node.op().name(), &target_params, &target_dag ))); @@ -609,22 +597,22 @@ fn replace_node( if node.params_view().is_empty() { 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); - let old_cargs = dag.get_cargs(node.clbits); + let old_qargs = dag.get_qargs(node.qubits()); + let old_cargs = dag.get_cargs(node.clbits()); let new_qubits: Vec = target_dag - .get_qargs(inner_node.qubits) + .get_qargs(inner_node.qubits()) .iter() .map(|qubit| old_qargs[qubit.0 as usize]) .collect(); let new_clbits: Vec = target_dag - .get_cargs(inner_node.clbits) + .get_cargs(inner_node.clbits()) .iter() .map(|clbit| old_cargs[clbit.0 as usize]) .collect(); - let new_op = if inner_node.op.try_standard_gate().is_none() { - inner_node.op.py_copy(py)? + let new_op = if inner_node.op().try_standard_gate().is_none() { + inner_node.op().py_copy(py)? } else { - inner_node.op.clone() + inner_node.op().clone() }; if node.condition().is_some() { match new_op.view() { @@ -646,17 +634,13 @@ fn replace_node( .iter() .map(|param| param.clone_ref(py)) .collect(); - let new_extra_props = node.extra_attrs.clone(); + let new_extra_props = node.extra_attrs().clone(); dag.apply_operation_back( py, new_op, &new_qubits, &new_clbits, - if new_params.is_empty() { - None - } else { - Some(new_params) - }, + (!new_params.is_empty()).then_some(new_params), new_extra_props, #[cfg(feature = "cache_pygates")] None, @@ -670,22 +654,22 @@ fn replace_node( .into_py_dict_bound(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); - let old_cargs = dag.get_cargs(node.clbits); + let old_qargs = dag.get_qargs(node.qubits()); + let old_cargs = dag.get_cargs(node.clbits()); let new_qubits: Vec = target_dag - .get_qargs(inner_node.qubits) + .get_qargs(inner_node.qubits()) .iter() .map(|qubit| old_qargs[qubit.0 as usize]) .collect(); let new_clbits: Vec = target_dag - .get_cargs(inner_node.clbits) + .get_cargs(inner_node.clbits()) .iter() .map(|clbit| old_cargs[clbit.0 as usize]) .collect(); - let new_op = if inner_node.op.try_standard_gate().is_none() { - inner_node.op.py_copy(py)? + let new_op = if inner_node.op().try_standard_gate().is_none() { + inner_node.op().py_copy(py)? } else { - inner_node.op.clone() + inner_node.op().clone() }; let mut new_params: SmallVec<[Param; 3]> = inner_node .params_view() @@ -756,12 +740,8 @@ fn replace_node( new_op, &new_qubits, &new_clbits, - if new_params.is_empty() { - None - } else { - Some(new_params) - }, - inner_node.extra_attrs.clone(), + (!new_params.is_empty()).then_some(new_params), + inner_node.extra_attrs().clone(), #[cfg(feature = "cache_pygates")] None, )?; diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs index 4d4702d18b49..80c8aaa5c24e 100644 --- a/crates/accelerate/src/check_map.rs +++ b/crates/accelerate/src/check_map.rs @@ -38,9 +38,9 @@ fn recurse<'py>( }; for node in dag.op_nodes(false) { if let NodeType::Operation(inst) = &dag.dag()[node] { - let qubits = dag.get_qargs(inst.qubits); - if inst.op.control_flow() { - if let OperationRef::Instruction(py_inst) = inst.op.view() { + let qubits = dag.get_qargs(inst.qubits()); + if inst.op().control_flow() { + 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() { @@ -71,7 +71,7 @@ fn recurse<'py>( && !check_qubits(qubits) { return Ok(Some(( - inst.op.name().to_string(), + inst.op().name().to_string(), [qubits[0].0, qubits[1].0], ))); } diff --git a/crates/accelerate/src/commutation_analysis.rs b/crates/accelerate/src/commutation_analysis.rs index 07266191fe45..30079beb008c 100644 --- a/crates/accelerate/src/commutation_analysis.rs +++ b/crates/accelerate/src/commutation_analysis.rs @@ -82,25 +82,25 @@ pub(crate) fn analyze_commutations_inner( if let (NodeType::Operation(packed_inst0), NodeType::Operation(packed_inst1)) = (&dag.dag()[current_gate_idx], &dag.dag()[*prev_gate_idx]) { - let op1 = packed_inst0.op.view(); - let op2 = packed_inst1.op.view(); + let op1 = packed_inst0.op().view(); + let op2 = packed_inst1.op().view(); let params1 = packed_inst0.params_view(); let params2 = packed_inst1.params_view(); - let qargs1 = dag.get_qargs(packed_inst0.qubits); - let qargs2 = dag.get_qargs(packed_inst1.qubits); - let cargs1 = dag.get_cargs(packed_inst0.clbits); - let cargs2 = dag.get_cargs(packed_inst1.clbits); + let qargs1 = dag.get_qargs(packed_inst0.qubits()); + let qargs2 = dag.get_qargs(packed_inst1.qubits()); + let cargs1 = dag.get_cargs(packed_inst0.clbits()); + let cargs2 = dag.get_cargs(packed_inst1.clbits()); all_commute = commutation_checker.commute_inner( py, &op1, params1, - &packed_inst0.extra_attrs, + packed_inst0.extra_attrs(), qargs1, cargs1, &op2, params2, - &packed_inst1.extra_attrs, + packed_inst1.extra_attrs(), qargs2, cargs2, MAX_NUM_QUBITS, diff --git a/crates/accelerate/src/commutation_cancellation.rs b/crates/accelerate/src/commutation_cancellation.rs index 8e4e9281e103..5264cb89805f 100644 --- a/crates/accelerate/src/commutation_cancellation.rs +++ b/crates/accelerate/src/commutation_cancellation.rs @@ -116,12 +116,12 @@ pub(crate) fn cancel_commutations( NodeType::Operation(instr) => instr, _ => panic!("Unexpected type in commutation set."), }; - let num_qargs = dag.get_qargs(instr.qubits).len(); + let num_qargs = dag.get_qargs(instr.qubits()).len(); // no support for cancellation of parameterized gates if instr.is_parameterized() { continue; } - if let Some(op_gate) = instr.op.try_standard_gate() { + if let Some(op_gate) = instr.op().try_standard_gate() { if num_qargs == 1 && SUPPORTED_GATES.contains(&op_gate) { cancellation_sets .entry(CancellationSetKey { @@ -158,8 +158,8 @@ pub(crate) fn cancel_commutations( } // Don't deal with Y rotation, because Y rotation doesn't commute with // CNOT, so it should be dealt with by optimized1qgate pass - if num_qargs == 2 && dag.get_qargs(instr.qubits)[0] == wire { - let second_qarg = dag.get_qargs(instr.qubits)[1]; + if num_qargs == 2 && dag.get_qargs(instr.qubits())[0] == wire { + let second_qarg = dag.get_qargs(instr.qubits())[1]; cancellation_sets .entry(CancellationSetKey { gate: GateOrRotation::Gate(op_gate), @@ -202,14 +202,14 @@ pub(crate) fn cancel_commutations( NodeType::Operation(instr) => instr, _ => panic!("Unexpected type in commutation set run."), }; - let node_op_name = node_op.op.name(); + let node_op_name = node_op.op().name(); let node_angle = if ROTATION_GATES.contains(&node_op_name) { match node_op.params_view().first() { Some(Param::Float(f)) => Ok(*f), _ => return Err(QiskitError::new_err(format!( "Rotational gate with parameter expression encountered in cancellation {:?}", - node_op.op + node_op.op() ))) } } else if HALF_TURNS.contains(&node_op_name) { @@ -227,7 +227,7 @@ pub(crate) fn cancel_commutations( total_angle += node_angle?; let Param::Float(new_phase) = node_op - .op + .op() .definition(node_op.params_view()) .unwrap() .global_phase() diff --git a/crates/accelerate/src/consolidate_blocks.rs b/crates/accelerate/src/consolidate_blocks.rs index 0fec3fa2909a..dacefbf0bf0a 100644 --- a/crates/accelerate/src/consolidate_blocks.rs +++ b/crates/accelerate/src/consolidate_blocks.rs @@ -104,8 +104,8 @@ pub(crate) fn consolidate_blocks( if !is_supported( target, basis_gates.as_ref(), - inst.op.name(), - dag.get_qargs(inst.qubits), + inst.op().name(), + dag.get_qargs(inst.qubits()), ) { all_block_gates.insert(inst_node); let matrix = match get_matrix_from_inst(py, inst) { @@ -124,16 +124,16 @@ pub(crate) fn consolidate_blocks( let mut outside_basis = false; for node in &block { let inst = dag.dag()[*node].unwrap_operation(); - block_qargs.extend(dag.get_qargs(inst.qubits)); + block_qargs.extend(dag.get_qargs(inst.qubits())); all_block_gates.insert(*node); - if inst.op.name() == basis_gate_name { + if inst.op().name() == basis_gate_name { basis_count += 1; } if !is_supported( target, basis_gates.as_ref(), - inst.op.name(), - dag.get_qargs(inst.qubits), + inst.op().name(), + dag.get_qargs(inst.qubits()), ) { outside_basis = true; } @@ -154,9 +154,12 @@ pub(crate) fn consolidate_blocks( let inst = dag.dag()[*node].unwrap_operation(); Ok(( - inst.op.clone(), - inst.params_view().iter().cloned().collect(), - dag.get_qargs(inst.qubits) + inst.op().clone(), + inst.params_view() + .iter() + .map(|param| param.clone_ref(py)) + .collect(), + dag.get_qargs(inst.qubits()) .iter() .map(|x| Qubit::new(block_index_map[x])) .collect(), @@ -243,13 +246,13 @@ pub(crate) fn consolidate_blocks( } let first_inst_node = run[0]; let first_inst = dag.dag()[first_inst_node].unwrap_operation(); - let first_qubits = dag.get_qargs(first_inst.qubits); + let first_qubits = dag.get_qargs(first_inst.qubits()); if run.len() == 1 && !is_supported( target, basis_gates.as_ref(), - first_inst.op.name(), + first_inst.op().name(), first_qubits, ) { diff --git a/crates/accelerate/src/convert_2q_block_matrix.rs b/crates/accelerate/src/convert_2q_block_matrix.rs index aefc5976e82f..135e2b22eb56 100644 --- a/crates/accelerate/src/convert_2q_block_matrix.rs +++ b/crates/accelerate/src/convert_2q_block_matrix.rs @@ -32,13 +32,13 @@ use crate::QiskitError; #[inline] pub fn get_matrix_from_inst(py: Python, inst: &PackedInstruction) -> PyResult> { - if let Some(mat) = inst.op.matrix(inst.params_view()) { + if let Some(mat) = inst.op().matrix(inst.params_view()) { Ok(mat) - } else if inst.op.try_standard_gate().is_some() { + } else if inst.op().try_standard_gate().is_some() { Err(QiskitError::new_err( "Parameterized gates can't be consolidated", )) - } else if let OperationRef::Gate(gate) = inst.op.view() { + } else if let OperationRef::Gate(gate) = inst.op().view() { Ok(QI_OPERATOR .get_bound(py) .call1((gate.gate.clone_ref(py),))? @@ -75,7 +75,7 @@ pub fn blocks_to_matrix( let inst = dag.dag()[*node].unwrap_operation(); let op_matrix = get_matrix_from_inst(py, inst)?; match dag - .get_qargs(inst.qubits) + .get_qargs(inst.qubits()) .iter() .map(map_bits) .collect::>() diff --git a/crates/accelerate/src/elide_permutations.rs b/crates/accelerate/src/elide_permutations.rs index 81dc0ba24027..511fb54761b1 100644 --- a/crates/accelerate/src/elide_permutations.rs +++ b/crates/accelerate/src/elide_permutations.rs @@ -40,20 +40,20 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult { - let qargs = dag.get_qargs(inst.qubits); + let qargs = dag.get_qargs(inst.qubits()); let index0 = qargs[0].index(); let index1 = qargs[1].index(); mapping.swap(index0, index1); } ("permutation", None) => { - if let Param::Obj(ref pyobj) = inst.params.as_ref().unwrap()[0] { + if let Param::Obj(ref pyobj) = inst.params_view()[0] { let pyarray: PyReadonlyArray1 = pyobj.extract(py)?; let pattern = pyarray.as_array(); let qindices: Vec = dag - .get_qargs(inst.qubits) + .get_qargs(inst.qubits()) .iter() .map(|q| q.index()) .collect(); @@ -75,8 +75,8 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult { // General instruction - let qargs = dag.get_qargs(inst.qubits); - let cargs = dag.get_cargs(inst.clbits); + let qargs = dag.get_qargs(inst.qubits()); + let cargs = dag.get_cargs(inst.clbits()); let mapped_qargs: Vec = qargs .iter() .map(|q| Qubit::new(mapping[q.index()])) @@ -84,13 +84,13 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult raw_run.len() as f64, }; let qubit: PhysicalQubit = if let NodeType::Operation(inst) = &dag.dag()[raw_run[0]] { - PhysicalQubit::new(dag.get_qargs(inst.qubits)[0].0) + PhysicalQubit::new(dag.get_qargs(inst.qubits())[0].0) } else { unreachable!("nodes in runs will always be op nodes") }; @@ -1178,9 +1178,9 @@ pub(crate) fn optimize_1q_gates_decomposition( let node = &dag.dag()[*node_index]; if let NodeType::Operation(inst) = node { if let Some(target) = target { - error *= compute_error_term_from_target(inst.op.name(), target, qubit); + error *= compute_error_term_from_target(inst.op().name(), target, qubit); } - inst.op.matrix(inst.params_view()).unwrap() + inst.op().matrix(inst.params_view()).unwrap() } else { unreachable!("Can only have op nodes here") } @@ -1219,7 +1219,7 @@ pub(crate) fn optimize_1q_gates_decomposition( if let Some(basis) = basis_gates { for node in &raw_run { if let NodeType::Operation(inst) = &dag.dag()[*node] { - if !basis.contains(inst.op.name()) { + if !basis.contains(inst.op().name()) { outside_basis = true; break; } diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index a20dfea00535..6d4128ac3044 100755 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -81,7 +81,7 @@ fn py_check_direction_target(py: Python, dag: &DAGCircuit, target: &Target) -> P PhysicalQubit::new(op_args[1].0) ]; - target.instruction_supported(inst.op.name(), Some(&qargs)) + target.instruction_supported(inst.op().name(), Some(&qargs)) }; check_gate_direction(py, dag, &target_check, None) @@ -110,9 +110,9 @@ where panic!("PackedInstruction is expected"); }; - let inst_qargs = dag.get_qargs(packed_inst.qubits); + let inst_qargs = dag.get_qargs(packed_inst.qubits()); - if let OperationRef::Instruction(py_inst) = packed_inst.op.view() { + if let OperationRef::Instruction(py_inst) = packed_inst.op().view() { if py_inst.control_flow() { let circuit_to_dag = imports::CIRCUIT_TO_DAG.get_bound(py); let py_inst = py_inst.instruction.bind(py); @@ -212,7 +212,7 @@ fn py_fix_direction_target( ]; // Take this path so Target can check for exact match of the parameterized gate's angle - if let OperationRef::Standard(std_gate) = inst.op.view() { + if let OperationRef::Standard(std_gate) = inst.op().view() { match std_gate { StandardGate::RXXGate | StandardGate::RYYGate @@ -228,14 +228,14 @@ fn py_fix_direction_target( .expect("These gates should have Python classes") .bind(py), ), - Some(inst.params_view().to_vec()), + (!inst.params_view().is_empty()).then_some(inst.params_view().to_vec()), ) .unwrap_or(false) } _ => {} } } - target.instruction_supported(inst.op.name(), Some(&qargs)) + target.instruction_supported(inst.op().name(), Some(&qargs)) }; fix_gate_direction(py, dag, &target_check, None).cloned() @@ -257,9 +257,9 @@ where for node in dag.op_nodes(false) { let packed_inst = dag.dag()[node].unwrap_operation(); - let op_args = dag.get_qargs(packed_inst.qubits); + let op_args = dag.get_qargs(packed_inst.qubits()); - if let OperationRef::Instruction(py_inst) = packed_inst.op.view() { + if let OperationRef::Instruction(py_inst) = packed_inst.op().view() { if py_inst.control_flow() { let dag_to_circuit = imports::DAG_TO_CIRCUIT.get_bound(py); @@ -315,7 +315,7 @@ where // If the op has a pre-defined replacement - replace if the other direction is supported otherwise error // If no pre-defined replacement for the op - if the other direction is supported error saying no pre-defined rule otherwise error saying op is not supported - if let OperationRef::Standard(std_gate) = packed_inst.op.view() { + if let OperationRef::Standard(std_gate) = packed_inst.op().view() { match std_gate { StandardGate::CXGate | StandardGate::ECRGate @@ -333,7 +333,7 @@ where return Err(TranspilerError::new_err(format!( "The circuit requires a connection between physical qubits {:?} for {}", op_args, - packed_inst.op.name() + packed_inst.op().name() ))); } } @@ -344,12 +344,12 @@ where if gate_complies(packed_inst, &[op_args1, op_args0]) || has_calibration_for_op_node(py, dag, packed_inst, &[op_args1, op_args0])? { - return Err(TranspilerError::new_err(format!("{} would be supported on {:?} if the direction was swapped, but no rules are known to do that. {:?} can be automatically flipped.", packed_inst.op.name(), op_args, vec!["cx", "cz", "ecr", "swap", "rzx", "rxx", "ryy", "rzz"]))); + return Err(TranspilerError::new_err(format!("{} would be supported on {:?} if the direction was swapped, but no rules are known to do that. {:?} can be automatically flipped.", packed_inst.op().name(), op_args, vec!["cx", "cz", "ecr", "swap", "rzx", "rxx", "ryy", "rzz"]))); // NOTE: Make sure to update the list of the supported gates if adding more replacements } else { return Err(TranspilerError::new_err(format!( "{} with parameters {:?} is not supported on qubits {:?} in either direction.", - packed_inst.op.name(), + packed_inst.op().name(), packed_inst.params_view(), op_args ))); @@ -358,7 +358,7 @@ where for (node, op_blocks) in ops_to_replace { let packed_inst = dag.dag()[node].unwrap_operation(); - let OperationRef::Instruction(py_inst) = packed_inst.op.view() else { + let OperationRef::Instruction(py_inst) = packed_inst.op().view() else { panic!("PyInstruction is expected"); }; let new_op = py_inst @@ -396,11 +396,15 @@ fn has_calibration_for_op_node( ( DAGOpNode { instruction: CircuitInstruction { - operation: packed_inst.op.clone(), + operation: packed_inst.op().clone(), qubits: py_args.unbind(), clbits: PyTuple::empty_bound(py).unbind(), - params: packed_inst.params_view().iter().cloned().collect(), - extra_attrs: packed_inst.extra_attrs.clone(), + params: packed_inst + .params_view() + .iter() + .map(|param| param.clone_ref(py)) + .collect(), + extra_attrs: packed_inst.extra_attrs().clone(), #[cfg(feature = "cache_pygates")] py_op: packed_inst.py_op.clone(), }, diff --git a/crates/accelerate/src/gates_in_basis.rs b/crates/accelerate/src/gates_in_basis.rs index 81dc5cd0284a..f5980afca27d 100644 --- a/crates/accelerate/src/gates_in_basis.rs +++ b/crates/accelerate/src/gates_in_basis.rs @@ -26,7 +26,7 @@ use qiskit_circuit::Qubit; fn any_gate_missing_from_target(dag: &DAGCircuit, target: &Target) -> PyResult { #[inline] fn is_universal(gate: &PackedInstruction) -> bool { - matches!(gate.op.name(), "barrier" | "store") + matches!(gate.op().name(), "barrier" | "store") } fn visit_gate( @@ -36,12 +36,12 @@ fn any_gate_missing_from_target(dag: &DAGCircuit, target: &Target) -> PyResult, ) -> PyResult { let qargs_mapped = SmallVec::from_iter(qargs.iter().map(|q| wire_map[q])); - if !target.instruction_supported(gate.op.name(), Some(&qargs_mapped)) { + if !target.instruction_supported(gate.op().name(), Some(&qargs_mapped)) { return Ok(true); } - if gate.op.control_flow() { - for block in gate.op.blocks() { + if gate.op().control_flow() { + for block in gate.op().blocks() { let block_qubits = (0..block.num_qubits()).map(Qubit::new); let inner_wire_map = qargs .iter() @@ -65,7 +65,7 @@ fn any_gate_missing_from_target(dag: &DAGCircuit, target: &Target) -> PyResult PyResult PyResult { - if gate_a.op.name() != gate_b.operation.name() { + if gate_a.op().name() != gate_b.operation.name() { return Ok(false); } let a_params = gate_a.params_view(); @@ -81,11 +81,11 @@ fn run_on_self_inverse( let next_qargs = if let NodeType::Operation(next_inst) = &dag.dag()[gate_cancel_run[i + 1]] { - next_inst.qubits + next_inst.qubits() } else { panic!("Not an op node") }; - if inst.qubits != next_qargs { + if inst.qubits() != next_qargs { partitions.push(std::mem::take(&mut chunk)); } } @@ -134,7 +134,7 @@ fn run_on_inverse_pairs( while i < nodes.len() - 1 { if let NodeType::Operation(inst) = &dag.dag()[nodes[i]] { if let NodeType::Operation(next_inst) = &dag.dag()[nodes[i + 1]] { - if inst.qubits == next_inst.qubits + if inst.qubits() == next_inst.qubits() && ((gate_eq(py, inst, &gate_0)? && gate_eq(py, next_inst, &gate_1)?) || (gate_eq(py, inst, &gate_1)? && gate_eq(py, next_inst, &gate_0)?)) diff --git a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs index 5e1ba4182344..d7713fc4f516 100644 --- a/crates/accelerate/src/remove_diagonal_gates_before_measure.rs +++ b/crates/accelerate/src/remove_diagonal_gates_before_measure.rs @@ -53,7 +53,7 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { panic!() }; - if inst.op.name() == "measure" { + if inst.op().name() == "measure" { let predecessor = (dag.quantum_predecessors(index)) .next() .expect("index is an operation node, so it must have a predecessor."); @@ -71,7 +71,7 @@ fn run_remove_diagonal_before_measure(dag: &mut DAGCircuit) -> PyResult<()> { .map(|s| { let node_s = &dag.dag()[s]; if let NodeType::Operation(inst_s) = node_s { - inst_s.op.name() == "measure" + inst_s.op().name() == "measure" } else { false } diff --git a/crates/accelerate/src/remove_identity_equiv.rs b/crates/accelerate/src/remove_identity_equiv.rs index a3eb921628e2..ac13af9cc532 100644 --- a/crates/accelerate/src/remove_identity_equiv.rs +++ b/crates/accelerate/src/remove_identity_equiv.rs @@ -42,11 +42,11 @@ fn remove_identity_equiv( match target { Some(target) => { let qargs: Vec = dag - .get_qargs(inst.qubits) + .get_qargs(inst.qubits()) .iter() .map(|x| PhysicalQubit::new(x.0)) .collect(); - let error_rate = target.get_error(inst.op.name(), qargs.as_slice()); + let error_rate = target.get_error(inst.op().name(), qargs.as_slice()); match error_rate { Some(err) => err * degree, None => f64::EPSILON.max(1. - degree), @@ -59,11 +59,11 @@ fn remove_identity_equiv( None => match target { Some(target) => { let qargs: Vec = dag - .get_qargs(inst.qubits) + .get_qargs(inst.qubits()) .iter() .map(|x| PhysicalQubit::new(x.0)) .collect(); - let error_rate = target.get_error(inst.op.name(), qargs.as_slice()); + let error_rate = target.get_error(inst.op().name(), qargs.as_slice()); match error_rate { Some(err) => err, None => f64::EPSILON, @@ -76,7 +76,7 @@ fn remove_identity_equiv( for op_node in dag.op_nodes(false) { let inst = dag.dag()[op_node].unwrap_operation(); - match inst.op.view() { + match inst.op().view() { OperationRef::Standard(gate) => { let (dim, trace) = match gate { StandardGate::RXGate | StandardGate::RYGate | StandardGate::RZGate => { diff --git a/crates/accelerate/src/split_2q_unitaries.rs b/crates/accelerate/src/split_2q_unitaries.rs index ac2577c2fc2c..f1b3065cdd3a 100644 --- a/crates/accelerate/src/split_2q_unitaries.rs +++ b/crates/accelerate/src/split_2q_unitaries.rs @@ -35,15 +35,15 @@ pub fn split_2q_unitaries( for node in nodes { if let NodeType::Operation(inst) = &dag.dag()[node] { - let qubits = dag.get_qargs(inst.qubits).to_vec(); + let qubits = dag.get_qargs(inst.qubits()).to_vec(); // We only attempt to split UnitaryGate objects, but this could be extended in future // -- however we need to ensure that we can compile the resulting single-qubit unitaries // to the supported basis gate set. - if qubits.len() != 2 || inst.op.name() != "unitary" { + if qubits.len() != 2 || inst.op().name() != "unitary" { continue; } let matrix = inst - .op + .op() .matrix(inst.params_view()) .expect("'unitary' gates should always have a matrix form"); let decomp = TwoQubitWeylDecomposition::new_inner( diff --git a/crates/accelerate/src/twirling.rs b/crates/accelerate/src/twirling.rs index 29c9da3671bc..6870d9e662d6 100644 --- a/crates/accelerate/src/twirling.rs +++ b/crates/accelerate/src/twirling.rs @@ -196,59 +196,51 @@ fn twirl_gate( twirl_set: &[([StandardGate; 4], f64)], inst: &PackedInstruction, ) -> PyResult<()> { - let qubits = circ.get_qargs(inst.qubits); + let qubits = circ.get_qargs(inst.qubits()); let (twirl, twirl_phase) = twirl_set.choose(rng).unwrap(); let bit_zero = out_circ.add_qargs(std::slice::from_ref(&qubits[0])); let bit_one = out_circ.add_qargs(std::slice::from_ref(&qubits[1])); out_circ.push( py, - PackedInstruction { - op: PackedOperation::from_standard(twirl[0]), - qubits: bit_zero, - clbits: circ.cargs_interner().get_default(), - params: None, - extra_attrs: ExtraInstructionAttributes::new(None, None, None, None), - #[cfg(feature = "cache_pygates")] - py_op: std::sync::OnceLock::new(), - }, + PackedInstruction::new( + PackedOperation::from_standard(twirl[0]), + bit_zero, + circ.cargs_interner().get_default(), + None, + ExtraInstructionAttributes::default(), + ), )?; out_circ.push( py, - PackedInstruction { - op: PackedOperation::from_standard(twirl[1]), - qubits: bit_one, - clbits: circ.cargs_interner().get_default(), - params: None, - extra_attrs: ExtraInstructionAttributes::new(None, None, None, None), - #[cfg(feature = "cache_pygates")] - py_op: std::sync::OnceLock::new(), - }, + PackedInstruction::new( + PackedOperation::from_standard(twirl[1]), + bit_one, + circ.cargs_interner().get_default(), + None, + ExtraInstructionAttributes::default(), + ), )?; out_circ.push(py, inst.clone())?; out_circ.push( py, - PackedInstruction { - op: PackedOperation::from_standard(twirl[2]), - qubits: bit_zero, - clbits: circ.cargs_interner().get_default(), - params: None, - extra_attrs: ExtraInstructionAttributes::new(None, None, None, None), - #[cfg(feature = "cache_pygates")] - py_op: std::sync::OnceLock::new(), - }, + PackedInstruction::new( + PackedOperation::from_standard(twirl[2]), + bit_zero, + circ.cargs_interner().get_default(), + None, + ExtraInstructionAttributes::default(), + ), )?; out_circ.push( py, - PackedInstruction { - op: PackedOperation::from_standard(twirl[3]), - qubits: bit_one, - clbits: circ.cargs_interner().get_default(), - params: None, - extra_attrs: ExtraInstructionAttributes::new(None, None, None, None), - #[cfg(feature = "cache_pygates")] - py_op: std::sync::OnceLock::new(), - }, + PackedInstruction::new( + PackedOperation::from_standard(twirl[3]), + bit_one, + circ.cargs_interner().get_default(), + None, + ExtraInstructionAttributes::default(), + ), )?; if *twirl_phase != 0. { @@ -271,12 +263,12 @@ fn generate_twirled_circuit( for inst in circ.data() { if let Some(custom_gate_map) = custom_gate_map { - if let Some(twirling_set) = custom_gate_map.get(inst.op.name()) { + if let Some(twirling_set) = custom_gate_map.get(inst.op().name()) { twirl_gate(py, circ, rng, &mut out_circ, twirling_set.as_slice(), inst)?; continue; } } - match inst.op.view() { + match inst.op().view() { OperationRef::Standard(gate) => match gate { StandardGate::CXGate => { if twirling_mask & CX_MASK != 0 { @@ -349,20 +341,18 @@ fn generate_twirled_circuit( control_flow: true, instruction: new_inst_obj.clone_ref(py), }; - let new_inst = PackedInstruction { - op: PackedOperation::from_instruction(Box::new(new_inst)), - qubits: inst.qubits, - clbits: inst.clbits, - params: Some(Box::new( + let new_inst = PackedInstruction::new( + PackedOperation::from_instruction(Box::new(new_inst)), + inst.qubits(), + inst.clbits(), + (!new_blocks.is_empty()).then_some( new_blocks .iter() - .map(|x| Param::Obj(x.into_py(py))) + .map(|x| x.extract(py).unwrap()) .collect::>(), - )), - extra_attrs: inst.extra_attrs.clone(), - #[cfg(feature = "cache_pygates")] - py_op: std::sync::OnceLock::new(), - }; + ), + inst.extra_attrs().clone(), + ); #[cfg(feature = "cache_pygates")] new_inst.py_op.set(new_inst_obj).unwrap(); out_circ.push(py, new_inst)?; diff --git a/crates/accelerate/src/unitary_synthesis.rs b/crates/accelerate/src/unitary_synthesis.rs index 62f41c78084c..11e1f1f8eeb3 100644 --- a/crates/accelerate/src/unitary_synthesis.rs +++ b/crates/accelerate/src/unitary_synthesis.rs @@ -113,12 +113,12 @@ fn apply_synth_dag( ) -> PyResult<()> { for out_node in synth_dag.topological_op_nodes()? { let mut out_packed_instr = synth_dag.dag()[out_node].unwrap_operation().clone(); - let synth_qargs = synth_dag.get_qargs(out_packed_instr.qubits); + let synth_qargs = synth_dag.get_qargs(out_packed_instr.qubits()); let mapped_qargs: Vec = synth_qargs .iter() .map(|qarg| out_qargs[qarg.0 as usize]) .collect(); - out_packed_instr.qubits = out_dag.qargs_interner.insert(&mapped_qargs); + *out_packed_instr.qubits_mut() = out_dag.qargs_interner.insert(&mapped_qargs); out_dag.push_back(py, out_packed_instr)?; } out_dag.add_global_phase(py, &synth_dag.get_global_phase())?; @@ -138,19 +138,17 @@ fn apply_synth_sequence( Some(gate) => *gate, }; let mapped_qargs: Vec = qubit_ids.iter().map(|id| out_qargs[*id as usize]).collect(); - let new_params: Option>> = match gate { - Some(_) => Some(Box::new(params.iter().map(|p| Param::Float(*p)).collect())), - None => Some(Box::new(sequence.decomp_gate.params.clone())), - }; - let instruction = PackedInstruction { - op: PackedOperation::from_standard(gate_node), - qubits: out_dag.qargs_interner.insert(&mapped_qargs), - clbits: out_dag.cargs_interner.get_default(), - params: new_params, - extra_attrs: ExtraInstructionAttributes::default(), - #[cfg(feature = "cache_pygates")] - py_op: OnceLock::new(), + let new_params: Option> = match gate { + Some(_) => Some(params.iter().map(|p| Param::Float(*p)).collect()), + None => Some(sequence.decomp_gate.params.clone()), }; + let instruction = PackedInstruction::new( + PackedOperation::from_standard(gate_node), + out_dag.qargs_interner.insert(&mapped_qargs), + out_dag.cargs_interner.get_default(), + new_params, + ExtraInstructionAttributes::default(), + ); instructions.push(instruction); } out_dag.extend(py, instructions.into_iter())?; @@ -239,8 +237,8 @@ fn py_run_main_loop( for node in dag.topological_op_nodes()? { let mut packed_instr = dag.dag()[node].unwrap_operation().clone(); - if packed_instr.op.control_flow() { - let OperationRef::Instruction(py_instr) = packed_instr.op.view() else { + if packed_instr.op().control_flow() { + let OperationRef::Instruction(py_instr) = packed_instr.op().view() else { unreachable!("Control flow op must be an instruction") }; let raw_blocks: Vec>> = py_instr @@ -252,7 +250,7 @@ fn py_run_main_loop( let mut new_blocks = Vec::with_capacity(raw_blocks.len()); for raw_block in raw_blocks { let new_ids = dag - .get_qargs(packed_instr.qubits) + .get_qargs(packed_instr.qubits()) .iter() .map(|qarg| qubit_indices[qarg.0 as usize]) .collect_vec(); @@ -279,30 +277,28 @@ fn py_run_main_loop( .bind(py) .call_method1("replace_blocks", (new_blocks,))?; let new_node_op: OperationFromPython = new_node.extract()?; - packed_instr = PackedInstruction { - op: new_node_op.operation, - qubits: packed_instr.qubits, - clbits: packed_instr.clbits, - params: (!new_node_op.params.is_empty()).then(|| Box::new(new_node_op.params)), - extra_attrs: new_node_op.extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op: new_node.unbind().into(), - }; + packed_instr = PackedInstruction::new( + new_node_op.operation, + packed_instr.qubits(), + packed_instr.clbits(), + (!new_node_op.params.is_empty()).then_some(new_node_op.params), + new_node_op.extra_attrs, + ); } - if !(packed_instr.op.name() == "unitary" - && packed_instr.op.num_qubits() >= min_qubits as u32) + if !(packed_instr.op().name() == "unitary" + && packed_instr.op().num_qubits() >= min_qubits as u32) { out_dag.push_back(py, packed_instr)?; continue; } - let unitary: Array, Dim<[usize; 2]>> = match packed_instr.op.matrix(&[]) { + let unitary: Array, Dim<[usize; 2]>> = match packed_instr.op().matrix(&[]) { Some(unitary) => unitary, None => return Err(QiskitError::new_err("Unitary not found")), }; match unitary.shape() { // Run 1q synthesis [2, 2] => { - let qubit = dag.get_qargs(packed_instr.qubits)[0]; + let qubit = dag.get_qargs(packed_instr.qubits())[0]; let target_basis_set = get_target_basis_set(target, PhysicalQubit::new(qubit.0)); let sequence = unitary_to_gate_sequence_inner( unitary.view(), @@ -338,7 +334,7 @@ fn py_run_main_loop( // Run 2q synthesis [4, 4] => { // "out_qargs" is used to append the synthesized instructions to the output dag - let out_qargs = dag.get_qargs(packed_instr.qubits); + let out_qargs = dag.get_qargs(packed_instr.qubits()); // "ref_qubits" is used to access properties in the target. It accounts for control flow mapping. let ref_qubits: &[PhysicalQubit; 2] = &[ PhysicalQubit::new(qubit_indices[out_qargs[0].0 as usize] as u32), @@ -489,13 +485,13 @@ fn run_2q_unitary_synthesis( unreachable!("DAG node must be an instruction") }; let inst_qubits = synth_dag - .get_qargs(inst.qubits) + .get_qargs(inst.qubits()) .iter() .map(|q| ref_qubits[q.0 as usize]) .collect(); ( - inst.op.name().to_string(), - inst.params.clone().map(|boxed| *boxed), + inst.op().name().to_string(), + (!inst.params_view().is_empty()).then_some(inst.params_view().into()), inst_qubits, ) }); @@ -998,8 +994,8 @@ fn synth_su4_dag( let mut synth_direction: Option> = None; for node in synth_dag.topological_op_nodes()? { let inst = &synth_dag.dag()[node].unwrap_operation(); - if inst.op.num_qubits() == 2 { - let qargs = synth_dag.get_qargs(inst.qubits); + if inst.op().num_qubits() == 2 { + let qargs = synth_dag.get_qargs(inst.qubits()); synth_direction = Some(vec![qargs[0].0, qargs[1].0]); } } @@ -1064,11 +1060,11 @@ fn reversed_synth_su4_dag( let mut inst = synth_dag.dag()[node].unwrap_operation().clone(); let qubits: Vec = synth_dag .qargs_interner() - .get(inst.qubits) + .get(inst.qubits()) .iter() .map(|x| flip_bits[x.0 as usize]) .collect(); - inst.qubits = target_dag.qargs_interner.insert_owned(qubits); + *inst.qubits_mut() = target_dag.qargs_interner.insert_owned(qubits); target_dag.push_back(py, inst)?; } Ok(target_dag) diff --git a/crates/circuit/src/circuit_data.rs b/crates/circuit/src/circuit_data.rs index 3ed429d4f5e0..fb4e0f371bbd 100644 --- a/crates/circuit/src/circuit_data.rs +++ b/crates/circuit/src/circuit_data.rs @@ -298,27 +298,23 @@ impl CircuitData { if deepcopy { let memo = PyDict::new_bound(py); for inst in &self.data { - res.data.push(PackedInstruction { - op: inst.op.py_deepcopy(py, Some(&memo))?, - qubits: inst.qubits, - clbits: inst.clbits, - params: inst.params.clone(), - extra_attrs: inst.extra_attrs.clone(), - #[cfg(feature = "cache_pygates")] - py_op: OnceLock::new(), - }); + res.data.push(PackedInstruction::new( + inst.op().py_deepcopy(py, Some(&memo))?, + inst.qubits(), + inst.clbits(), + (!inst.params_view().is_empty()).then_some(inst.params_view().into()), + inst.extra_attrs().clone(), + )); } } else if copy_instructions { for inst in &self.data { - res.data.push(PackedInstruction { - op: inst.op.py_copy(py)?, - qubits: inst.qubits, - clbits: inst.clbits, - params: inst.params.clone(), - extra_attrs: inst.extra_attrs.clone(), - #[cfg(feature = "cache_pygates")] - py_op: OnceLock::new(), - }); + res.data.push(PackedInstruction::new( + inst.op().py_copy(py)?, + inst.qubits(), + inst.clbits(), + (!inst.params_view().is_empty()).then_some(inst.params_view().into()), + inst.extra_attrs().clone(), + )); } } else { res.data.extend(self.data.iter().cloned()); @@ -345,10 +341,10 @@ impl CircuitData { let qubits = PySet::empty_bound(py)?; let clbits = PySet::empty_bound(py)?; for inst in self.data.iter() { - for b in self.qargs_interner.get(inst.qubits) { + for b in self.qargs_interner.get(inst.qubits()) { qubits.add(self.qubits.get(*b).unwrap().clone_ref(py))?; } - for b in self.cargs_interner.get(inst.clbits) { + for b in self.cargs_interner.get(inst.clbits()) { clbits.add(self.clbits.get(*b).unwrap().clone_ref(py))?; } } @@ -398,14 +394,15 @@ impl CircuitData { #[pyo3(signature = (func))] pub fn map_nonstandard_ops(&mut self, py: Python<'_>, func: &Bound) -> PyResult<()> { for inst in self.data.iter_mut() { - if inst.op.try_standard_gate().is_some() && inst.extra_attrs.condition().is_none() { + if inst.op().try_standard_gate().is_some() && inst.extra_attrs().condition().is_none() { continue; } let py_op = func.call1((inst.unpack_py_op(py)?,))?; let result = py_op.extract::()?; - inst.op = result.operation; - inst.params = (!result.params.is_empty()).then(|| Box::new(result.params)); - inst.extra_attrs = result.extra_attrs; + *inst.op_mut() = result.operation; + *inst.params_mut() = + (!result.params.is_empty()).then_some(result.params.clone().into()); + *inst.extra_attrs_mut() = result.extra_attrs; #[cfg(feature = "cache_pygates")] { inst.py_op = py_op.unbind().into(); @@ -505,14 +502,18 @@ impl CircuitData { // Get a single item, assuming the index is validated as in bounds. let get_single = |index: usize| { let inst = &self.data[index]; - let qubits = self.qargs_interner.get(inst.qubits); - let clbits = self.cargs_interner.get(inst.clbits); + let qubits = self.qargs_interner.get(inst.qubits()); + let clbits = self.cargs_interner.get(inst.clbits()); CircuitInstruction { - operation: inst.op.clone(), + 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(), - params: inst.params_view().iter().cloned().collect(), - extra_attrs: inst.extra_attrs.clone(), + params: inst + .params_view() + .iter() + .map(|param| param.clone_ref(py)) + .collect(), + extra_attrs: inst.extra_attrs().clone(), #[cfg(feature = "cache_pygates")] py_op: inst.py_op.clone(), } @@ -663,7 +664,7 @@ impl CircuitData { for inst in other.data.iter() { let qubits = other .qargs_interner - .get(inst.qubits) + .get(inst.qubits()) .iter() .map(|b| { Ok(self @@ -674,7 +675,7 @@ impl CircuitData { .collect::>>()?; let clbits = other .cargs_interner - .get(inst.clbits) + .get(inst.clbits()) .iter() .map(|b| { Ok(self @@ -686,15 +687,13 @@ impl CircuitData { let new_index = self.data.len(); let qubits_id = self.qargs_interner.insert_owned(qubits); let clbits_id = self.cargs_interner.insert_owned(clbits); - self.data.push(PackedInstruction { - op: inst.op.clone(), - qubits: qubits_id, - clbits: clbits_id, - params: inst.params.clone(), - extra_attrs: inst.extra_attrs.clone(), - #[cfg(feature = "cache_pygates")] - py_op: inst.py_op.clone(), - }); + self.data.push(PackedInstruction::new( + inst.op().clone(), + qubits_id, + clbits_id, + (!inst.params_view().is_empty()).then_some(inst.params_view().into()), + inst.extra_attrs().clone(), + )); self.track_instruction_parameters(py, new_index)?; } return Ok(()); @@ -768,7 +767,7 @@ impl CircuitData { pub fn count_ops(&self) -> IndexMap<&str, usize, ::ahash::RandomState> { let mut ops_count: IndexMap<&str, usize, ::ahash::RandomState> = IndexMap::default(); for instruction in &self.data { - *ops_count.entry(instruction.op.name()).or_insert(0) += 1; + *ops_count.entry(instruction.op().name()).or_insert(0) += 1; } ops_count.par_sort_by(|_k1, v1, _k2, v2| v2.cmp(v1)); ops_count @@ -881,7 +880,7 @@ impl CircuitData { pub fn num_nonlocal_gates(&self) -> usize { self.data .iter() - .filter(|inst| inst.op.num_qubits() > 1 && !inst.op.directive()) + .filter(|inst| inst.op().num_qubits() > 1 && !inst.op().directive()) .count() } } @@ -934,16 +933,14 @@ impl CircuitData { let (operation, params, qargs, cargs) = item?; let qubits = res.qargs_interner.insert_owned(qargs); let clbits = res.cargs_interner.insert_owned(cargs); - let params = (!params.is_empty()).then(|| Box::new(params)); - res.data.push(PackedInstruction { - op: operation, + let params = (!params.is_empty()).then_some(params); + res.data.push(PackedInstruction::new( + operation, qubits, clbits, params, - extra_attrs: ExtraInstructionAttributes::default(), - #[cfg(feature = "cache_pygates")] - py_op: OnceLock::new(), - }); + ExtraInstructionAttributes::default(), + )); res.track_instruction_parameters(py, res.data.len() - 1)?; } Ok(res) @@ -1043,16 +1040,14 @@ impl CircuitData { let no_clbit_index = res.cargs_interner.get_default(); for (operation, params, qargs) in instruction_iter { let qubits = res.qargs_interner.insert(&qargs); - let params = (!params.is_empty()).then(|| Box::new(params)); - res.data.push(PackedInstruction { - op: operation.into(), + let params = (!params.is_empty()).then_some(params); + res.data.push(PackedInstruction::new( + operation.into(), qubits, - clbits: no_clbit_index, + no_clbit_index, params, - extra_attrs: ExtraInstructionAttributes::default(), - #[cfg(feature = "cache_pygates")] - py_op: OnceLock::new(), - }); + ExtraInstructionAttributes::default(), + )); res.track_instruction_parameters(py, res.data.len() - 1)?; } Ok(res) @@ -1100,17 +1095,15 @@ impl CircuitData { qargs: &[Qubit], ) -> PyResult<()> { let no_clbit_index = self.cargs_interner.get_default(); - let params = (!params.is_empty()).then(|| Box::new(params.iter().cloned().collect())); + let params = (!params.is_empty()).then(|| params.iter().cloned().collect()); let qubits = self.qargs_interner.insert(qargs); - self.data.push(PackedInstruction { - op: operation.into(), + self.data.push(PackedInstruction::new( + operation.into(), qubits, - clbits: no_clbit_index, + no_clbit_index, params, - extra_attrs: ExtraInstructionAttributes::default(), - #[cfg(feature = "cache_pygates")] - py_op: OnceLock::new(), - }); + ExtraInstructionAttributes::default(), + )); Ok(()) } @@ -1194,19 +1187,17 @@ impl CircuitData { fn pack(&mut self, py: Python, inst: &CircuitInstruction) -> PyResult { let qubits = self .qargs_interner - .insert_owned(self.qubits.map_bits(inst.qubits.bind(py))?.collect()); + .insert_owned(self.qubits().map_bits(inst.qubits.bind(py))?.collect()); let clbits = self .cargs_interner - .insert_owned(self.clbits.map_bits(inst.clbits.bind(py))?.collect()); - Ok(PackedInstruction { - op: inst.operation.clone(), + .insert_owned(self.clbits().map_bits(inst.clbits.bind(py))?.collect()); + Ok(PackedInstruction::new( + inst.operation.clone(), qubits, clbits, - params: (!inst.params.is_empty()).then(|| Box::new(inst.params.clone())), - extra_attrs: inst.extra_attrs.clone(), - #[cfg(feature = "cache_pygates")] - py_op: inst.py_op.clone(), - }) + (!inst.params.is_empty()).then_some(inst.params.clone()), + inst.extra_attrs.clone(), + )) } /// Returns an iterator over all the instructions present in the circuit. @@ -1359,7 +1350,7 @@ impl CircuitData { let parameter = parameter as usize; let previous = &mut self.data[instruction]; if let Some(standard) = previous.standard_gate() { - let params = previous.params_mut(); + let params = previous.params_view_mut(); let Param::ParameterExpression(expr) = ¶ms[parameter] else { return Err(inconsistent()); }; @@ -1451,9 +1442,11 @@ impl CircuitData { }; op.getattr(params_attr)?.set_item(parameter, new_param)?; let mut new_op = op.extract::()?; - previous.op = new_op.operation; - previous.params_mut().swap_with_slice(&mut new_op.params); - previous.extra_attrs = new_op.extra_attrs; + *previous.op_mut() = new_op.operation; + previous + .params_view_mut() + .swap_with_slice(&mut new_op.params); + *previous.extra_attrs_mut() = new_op.extra_attrs; #[cfg(feature = "cache_pygates")] { previous.py_op = op.into_py(py).into(); @@ -1474,7 +1467,8 @@ impl CircuitData { // We only put non-standard gates in `user_operations`, so we're not risking creating a // previously non-existent Python object. let instruction = &self.data[instruction]; - let definition_cache = if matches!(instruction.op.view(), OperationRef::Operation(_)) { + let definition_cache = if matches!(instruction.op().view(), OperationRef::Operation(_)) + { // `Operation` instances don't have a `definition` as part of their interfaces, but // they might be an `AnnotatedOperation`, which is one of our special built-ins. // This should be handled more completely in the user-customisation interface by a diff --git a/crates/circuit/src/converters.rs b/crates/circuit/src/converters.rs index 030a582bdad7..d0c676e572cb 100644 --- a/crates/circuit/src/converters.rs +++ b/crates/circuit/src/converters.rs @@ -112,22 +112,20 @@ pub fn dag_to_circuit( ) }; if copy_operations { - let op = instr.op.py_deepcopy(py, None)?; - Ok(PackedInstruction { + let op = instr.op().py_deepcopy(py, None)?; + Ok(PackedInstruction::new( op, - qubits: instr.qubits, - clbits: instr.clbits, - params: Some(Box::new( + instr.qubits(), + instr.clbits(), + (!instr.params_view().is_empty()).then_some( instr .params_view() .iter() .map(|param| param.clone_ref(py)) .collect(), - )), - extra_attrs: instr.extra_attrs.clone(), - #[cfg(feature = "cache_pygates")] - py_op: OnceLock::new(), - }) + ), + instr.extra_attrs().clone(), + )) } else { Ok(instr.clone()) } diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 9b2a78127826..2d2d64afa56e 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -1008,7 +1008,7 @@ def _format(operand): let mut to_remove = Vec::new(); for (id, weight) in self.dag.node_references() { if let NodeType::Operation(packed) = &weight { - if opname == packed.op.name() { + if opname == packed.op().name() { to_remove.push(id); } } @@ -1286,9 +1286,9 @@ def _format(operand): for node_weight in self.dag.node_weights_mut() { match node_weight { NodeType::Operation(op) => { - let cargs = self.cargs_interner.get(op.clbits); + let cargs = self.cargs_interner.get(op.clbits()); let carg_bits = old_clbits.map_indices(cargs).map(|b| b.bind(py).clone()); - op.clbits = self + *op.clbits_mut() = self .cargs_interner .insert_owned(self.clbits.map_bits(carg_bits)?.collect()); } @@ -1494,9 +1494,9 @@ def _format(operand): for node_weight in self.dag.node_weights_mut() { match node_weight { NodeType::Operation(op) => { - let qargs = self.qargs_interner.get(op.qubits); + let qargs = self.qargs_interner.get(op.qubits()); let qarg_bits = old_qubits.map_indices(qargs).map(|b| b.bind(py).clone()); - op.qubits = self + *op.qubits_mut() = self .qargs_interner .insert_owned(self.qubits.map_bits(qarg_bits)?.collect()); } @@ -1762,15 +1762,13 @@ def _format(operand): let clbits_id = self .cargs_interner .insert_owned(self.clbits.map_bits(cargs.iter().flatten())?.collect()); - let instr = PackedInstruction { - op: py_op.operation, - qubits: qubits_id, - clbits: clbits_id, - params: (!py_op.params.is_empty()).then(|| Box::new(py_op.params)), - extra_attrs: py_op.extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op: op.unbind().into(), - }; + let instr = PackedInstruction::new( + py_op.operation, + qubits_id, + clbits_id, + (!py_op.params.is_empty()).then_some(py_op.params), + py_op.extra_attrs, + ); if check { self.check_op_addition(py, &instr)?; @@ -1816,15 +1814,13 @@ def _format(operand): let clbits_id = self .cargs_interner .insert_owned(self.clbits.map_bits(cargs.iter().flatten())?.collect()); - let instr = PackedInstruction { - op: py_op.operation, - qubits: qubits_id, - clbits: clbits_id, - params: (!py_op.params.is_empty()).then(|| Box::new(py_op.params)), - extra_attrs: py_op.extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op: op.unbind().into(), - }; + let instr = PackedInstruction::new( + py_op.operation, + qubits_id, + clbits_id, + (!py_op.params.is_empty()).then_some(py_op.params), + py_op.extra_attrs, + ); if check { self.check_op_addition(py, &instr)?; @@ -2068,7 +2064,7 @@ def _format(operand): let m_qargs = { let qubits = other .qubits - .map_indices(other.qargs_interner.get(op.qubits)); + .map_indices(other.qargs_interner.get(op.qubits())); let mut mapped = Vec::with_capacity(qubits.len()); for bit in qubits { mapped.push( @@ -2082,7 +2078,7 @@ def _format(operand): let m_cargs = { let clbits = other .clbits - .map_indices(other.cargs_interner.get(op.clbits)); + .map_indices(other.cargs_interner.get(op.clbits())); let mut mapped = Vec::with_capacity(clbits.len()); for bit in clbits { mapped.push( @@ -2104,7 +2100,7 @@ def _format(operand): if let Some(condition) = op.condition() { // TODO: do we need to check for condition.is_none()? let condition = variable_mapper.map_condition(condition.bind(py), true)?; - if !op.op.control_flow() { + if !op.op().control_flow() { py_op = py_op.call_method1( intern!(py, "c_if"), condition.downcast::()?, @@ -2180,7 +2176,7 @@ def _format(operand): let nodes_found = self.nodes_on_wire(&wire, true).into_iter().any(|node| { let weight = self.dag.node_weight(node).unwrap(); if let NodeType::Operation(packed) = weight { - !ignore_set.contains(packed.op.name()) + !ignore_set.contains(packed.op().name()) } else { false } @@ -2246,10 +2242,10 @@ def _format(operand): let NodeType::Operation(node) = node else { continue; }; - if !node.op.control_flow() { + if !node.op().control_flow() { continue; } - let OperationRef::Instruction(inst) = node.op.view() else { + let OperationRef::Instruction(inst) = node.op().view() else { panic!("control flow op must be an instruction"); }; let inst_bound = inst.instruction.bind(py); @@ -2326,10 +2322,10 @@ def _format(operand): let NodeType::Operation(node) = node else { continue; }; - if !node.op.control_flow() { + if !node.op().control_flow() { continue; } - let OperationRef::Instruction(inst) = node.op.view() else { + let OperationRef::Instruction(inst) = node.op().view() else { panic!("control flow op must be an instruction") }; let inst_bound = inst.instruction.bind(py); @@ -2534,15 +2530,15 @@ def _format(operand): let node_match = |n1: &NodeType, n2: &NodeType| -> PyResult { match [n1, n2] { [NodeType::Operation(inst1), NodeType::Operation(inst2)] => { - if inst1.op.name() != inst2.op.name() { + if inst1.op().name() != inst2.op().name() { return Ok(false); } let check_args = || -> bool { - let node1_qargs = self.qargs_interner.get(inst1.qubits); - let node2_qargs = other.qargs_interner.get(inst2.qubits); - let node1_cargs = self.cargs_interner.get(inst1.clbits); - let node2_cargs = other.cargs_interner.get(inst2.clbits); - if SEMANTIC_EQ_SYMMETRIC.contains(&inst1.op.name()) { + let node1_qargs = self.qargs_interner.get(inst1.qubits()); + let node2_qargs = other.qargs_interner.get(inst2.qubits()); + let node1_cargs = self.cargs_interner.get(inst1.clbits()); + let node2_cargs = other.cargs_interner.get(inst2.clbits()); + if SEMANTIC_EQ_SYMMETRIC.contains(&inst1.op().name()) { let node1_qargs = node1_qargs.iter().copied().collect::>(); let node2_qargs = @@ -2560,8 +2556,8 @@ def _format(operand): true }; let check_conditions = || -> PyResult { - if let Some(cond1) = inst1.extra_attrs.condition() { - if let Some(cond2) = inst2.extra_attrs.condition() { + if let Some(cond1) = inst1.extra_attrs().condition() { + if let Some(cond2) = inst2.extra_attrs().condition() { legacy_condition_eq .call1((cond1, cond2, &self_bit_indices, &other_bit_indices))? .extract::() @@ -2569,11 +2565,11 @@ def _format(operand): Ok(false) } } else { - Ok(inst2.extra_attrs.condition().is_none()) + Ok(inst2.extra_attrs().condition().is_none()) } }; - match [inst1.op.view(), inst2.op.view()] { + match [inst1.op().view(), inst2.op().view()] { [OperationRef::Standard(_op1), OperationRef::Standard(_op2)] => { Ok(inst1.py_op_eq(py, inst2)? && check_args() @@ -2875,7 +2871,7 @@ def _format(operand): let cargs_set = cargs_set.downcast::().unwrap(); if !propagate_condition && self.may_have_additional_wires(py, &node) { let (add_cargs, _add_vars) = - self.additional_wires(py, node.op.view(), node.condition())?; + self.additional_wires(py, node.op().view(), node.condition())?; for wire in add_cargs.iter() { let clbit = &self.clbits.get(*wire).unwrap(); if !cargs_set.contains(clbit.clone_ref(py))? { @@ -2977,7 +2973,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())?; + self.additional_wires(py, node.op().view(), node.condition())?; let var_set = PySet::new_bound(py, &additional_vars)?; if input_dag_var_set .call_method1(intern!(py, "difference"), (var_set.clone(),))? @@ -3034,10 +3030,10 @@ def _format(operand): let mut new_input_dag: Option = None; // It doesn't make sense to try and propagate a condition from a control-flow op; a // replacement for the control-flow op should implement the operation completely. - let node_map = if propagate_condition && !node.op.control_flow() { + let node_map = if propagate_condition && !node.op().control_flow() { // Nested until https://github.com/rust-lang/rust/issues/53667 is fixed in a stable // release - if let Some(condition) = node.extra_attrs.condition() { + if let Some(condition) = node.extra_attrs().condition() { let mut in_dag = input_dag.copy_empty_like(py, "alike")?; // The remapping of `condition` below is still using the old code that assumes a 2-tuple. // This is because this remapping code only makes sense in the case of non-control-flow @@ -3130,12 +3126,12 @@ def _format(operand): for in_node_index in input_dag.topological_op_nodes()? { let in_node = &input_dag.dag[in_node_index]; if let NodeType::Operation(inst) = in_node { - if inst.extra_attrs.condition().is_some() { + if inst.extra_attrs().condition().is_some() { return Err(DAGCircuitError::new_err( "cannot propagate a condition to an element that already has one", )); } - let cargs = input_dag.cargs_interner.get(inst.clbits); + let cargs = input_dag.cargs_interner.get(inst.clbits()); let cargs_bits: Vec = input_dag .clbits .map_indices(cargs) @@ -3151,12 +3147,8 @@ def _format(operand): let mut new_inst = inst.clone(); if new_condition.is_truthy()? { new_inst - .extra_attrs + .extra_attrs_mut() .set_condition(Some(new_condition.as_any().clone().unbind())); - #[cfg(feature = "cache_pygates")] - { - new_inst.py_op.take(); - } } in_dag.push_back(py, new_inst)?; } @@ -3232,12 +3224,12 @@ def _format(operand): None => &input_dag.dag[*old_node_index], }; if let NodeType::Operation(old_inst) = old_node { - if let OperationRef::Instruction(old_op) = old_inst.op.view() { + if let OperationRef::Instruction(old_op) = old_inst.op().view() { 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); - kwargs.set_item("label", old_inst.extra_attrs.label())?; + kwargs.set_item("label", old_inst.extra_attrs().label())?; let new_op = imports::SWITCH_CASE_OP.get_bound(py).call( ( variable_mapper.map_target(target)?, @@ -3250,7 +3242,7 @@ def _format(operand): if let NodeType::Operation(ref mut new_inst) = &mut self.dag[*new_node_index] { - new_inst.op = PyInstruction { + *new_inst.op_mut() = PyInstruction { qubits: old_op.num_qubits(), clbits: old_op.num_clbits(), params: old_op.num_params(), @@ -3266,8 +3258,8 @@ def _format(operand): } } } - if let Some(condition) = old_inst.extra_attrs.condition() { - if old_inst.op.name() != "switch_case" { + if let Some(condition) = old_inst.extra_attrs().condition() { + if old_inst.op().name() != "switch_case" { let new_condition: Option = variable_mapper .map_condition(condition.bind(py), false)? .extract()?; @@ -3276,12 +3268,14 @@ def _format(operand): if let NodeType::Operation(ref mut new_inst) = &mut self.dag[*new_node_index] { - new_inst.extra_attrs.set_condition(new_condition.clone()); + new_inst + .extra_attrs_mut() + .set_condition(new_condition.clone()); #[cfg(feature = "cache_pygates")] { new_inst.py_op.take(); } - match new_inst.op.view() { + match new_inst.op().view() { OperationRef::Instruction(py_inst) => { py_inst .instruction @@ -3349,8 +3343,12 @@ def _format(operand): let new_weight = self.dag[node_index].unwrap_operation(); let temp: OperationFromPython = op.extract()?; node.instruction.operation = temp.operation; - node.instruction.params = new_weight.params_view().iter().cloned().collect(); - node.instruction.extra_attrs = new_weight.extra_attrs.clone(); + node.instruction.params = new_weight + .params_view() + .iter() + .map(|param| param.clone_ref(py)) + .collect(); + node.instruction.extra_attrs = new_weight.extra_attrs().clone(); #[cfg(feature = "cache_pygates")] { node.instruction.py_op = new_weight.py_op.clone(); @@ -3426,7 +3424,7 @@ def _format(operand): } NodeType::Operation(pi) => { let new_node = new_dag.dag.add_node(NodeType::Operation(pi.clone())); - new_dag.increment_op(pi.op.name()); + new_dag.increment_op(pi.op().name()); node_map.insert(*node, new_node); non_classical = true; } @@ -3686,15 +3684,15 @@ def _format(operand): }; for (node, weight) in self.dag.node_references() { if let NodeType::Operation(packed) = &weight { - if !include_directives && packed.op.directive() { + if !include_directives && packed.op().directive() { continue; } if let Some(op_type) = op { // This middle catch is to avoid Python-space operation creation for most uses of // `op`; we're usually just looking for control-flow ops, and standard gates // aren't control-flow ops. - if !(filter_is_nonstandard && packed.op.try_standard_gate().is_some()) - && packed.op.py_op_is_instance(op_type)? + if !(filter_is_nonstandard && packed.op().try_standard_gate().is_some()) + && packed.op().py_op_is_instance(op_type)? { nodes.push(self.unpack_into(py, node, weight)?); } @@ -3718,7 +3716,7 @@ def _format(operand): .node_references() .filter_map(|(node_index, node_type)| match node_type { NodeType::Operation(ref node) => { - if node.op.control_flow() { + if node.op().control_flow() { Some(self.unpack_into(py, node_index, node_type)) } else { None @@ -3741,7 +3739,7 @@ def _format(operand): self.dag .node_references() .filter_map(|(node, weight)| match weight { - NodeType::Operation(ref packed) => match packed.op.view() { + NodeType::Operation(ref packed) => match packed.op().view() { OperationRef::Gate(_) | OperationRef::Standard(_) => { Some(self.unpack_into(py, node, weight)) } @@ -3762,7 +3760,7 @@ def _format(operand): let mut result: Vec> = Vec::new(); for (id, weight) in self.dag.node_references() { if let NodeType::Operation(ref packed) = weight { - if names_set.contains(packed.op.name()) { + if names_set.contains(packed.op().name()) { result.push(self.unpack_into(py, id, weight)?); } } @@ -3786,11 +3784,11 @@ def _format(operand): let mut nodes = Vec::new(); for (node, weight) in self.dag.node_references() { if let NodeType::Operation(ref packed) = weight { - if packed.op.directive() { + if packed.op().directive() { continue; } - let qargs = self.qargs_interner.get(packed.qubits); + let qargs = self.qargs_interner.get(packed.qubits()); if qargs.len() >= 3 { nodes.push(self.unpack_into(py, node, weight)?); } @@ -4152,7 +4150,7 @@ def _format(operand): py, new_layer .qubits - .map_indices(new_layer.qargs_interner.get(node.qubits)), + .map_indices(new_layer.qargs_interner.get(node.qubits())), ) }); let support_list = PyList::empty_bound(py); @@ -4185,14 +4183,14 @@ def _format(operand): let qubits = PyTuple::new_bound( py, self.qargs_interner - .get(retrieved_node.qubits) + .get(retrieved_node.qubits()) .iter() .map(|qubit| self.qubits.get(*qubit)), ) .unbind(); new_layer.push_back(py, retrieved_node.clone())?; - if !retrieved_node.op.directive() { + if !retrieved_node.op().directive() { support_list.append(qubits)?; } @@ -4375,7 +4373,7 @@ def _format(operand): let mut op_counts: HashMap<&str, usize> = HashMap::with_capacity(longest_path.len() - 2); for node_index in &longest_path[1..longest_path.len() - 1] { if let NodeType::Operation(ref packed) = self.dag[*node_index] { - let name = packed.op.name(); + let name = packed.op().name(); op_counts .entry(name) .and_modify(|count| *count += 1) @@ -4434,14 +4432,14 @@ def _format(operand): let cur_index = queue.pop_front().unwrap(); if let NodeType::Operation(packed) = self.dag.node_weight(cur_index).unwrap() { - if !packed.op.directive() { + if !packed.op().directive() { // If the operation is not a directive (in particular not a barrier nor a measure), // we do not do anything if it was already processed. Otherwise, we add its qubits // to qubits_in_cone, and append its predecessors to queue. if processed_non_directive_nodes.contains(&cur_index) { continue; } - qubits_in_cone.extend(self.qargs_interner.get(packed.qubits)); + qubits_in_cone.extend(self.qargs_interner.get(packed.qubits())); processed_non_directive_nodes.insert(cur_index); for pred_index in self.quantum_predecessors(cur_index) { @@ -4461,7 +4459,7 @@ def _format(operand): { if self .qargs_interner - .get(pred_packed.qubits) + .get(pred_packed.qubits()) .iter() .any(|x| qubits_in_cone.contains(x)) { @@ -4819,15 +4817,15 @@ impl DAGCircuit { &self, namelist: HashSet, ) -> impl Iterator> + '_ { - let filter_fn = move |node_index: NodeIndex| -> Result { - let node = &self.dag[node_index]; - match node { - NodeType::Operation(inst) => { - Ok(namelist.contains(inst.op.name()) && inst.extra_attrs.condition().is_none()) + let filter_fn = + move |node_index: NodeIndex| -> Result { + let node = &self.dag[node_index]; + match node { + NodeType::Operation(inst) => Ok(namelist.contains(inst.op().name()) + && inst.extra_attrs().condition().is_none()), + _ => Ok(false), } - _ => Ok(false), - } - }; + }; match rustworkx_core::dag_algo::collect_runs(&self.dag, filter_fn) { Some(iter) => iter.map(|result| result.unwrap()), @@ -4840,11 +4838,11 @@ impl DAGCircuit { let filter_fn = move |node_index: NodeIndex| -> Result { let node = &self.dag[node_index]; match node { - NodeType::Operation(inst) => Ok(inst.op.num_qubits() == 1 - && inst.op.num_clbits() == 0 + NodeType::Operation(inst) => Ok(inst.op().num_qubits() == 1 + && inst.op().num_clbits() == 0 && !inst.is_parameterized() - && (inst.op.try_standard_gate().is_some() - || inst.op.matrix(inst.params_view()).is_some()) + && (inst.op().try_standard_gate().is_some() + || inst.op().matrix(inst.params_view()).is_some()) && inst.condition().is_none()), _ => Ok(false), } @@ -4858,7 +4856,7 @@ impl DAGCircuit { let filter_fn = move |node_index: NodeIndex| -> Result, Infallible> { let node = &self.dag[node_index]; match node { - NodeType::Operation(inst) => match inst.op.view() { + NodeType::Operation(inst) => match inst.op().view() { OperationRef::Standard(gate) => Ok(Some( gate.num_qubits() <= 2 && inst.condition().is_none() @@ -4939,25 +4937,25 @@ impl DAGCircuit { /// another that was created from the first via /// [DAGCircuit::copy_empty_like]. pub fn push_back(&mut self, py: Python, instr: PackedInstruction) -> PyResult { - let op_name = instr.op.name(); + let op_name = instr.op().name(); let (all_cbits, vars): (Vec, Option>) = { if self.may_have_additional_wires(py, &instr) { let mut clbits: HashSet = - HashSet::from_iter(self.cargs_interner.get(instr.clbits).iter().copied()); + HashSet::from_iter(self.cargs_interner.get(instr.clbits()).iter().copied()); let (additional_clbits, additional_vars) = - self.additional_wires(py, instr.op.view(), instr.condition())?; + self.additional_wires(py, instr.op().view(), instr.condition())?; for clbit in additional_clbits { clbits.insert(clbit); } (clbits.into_iter().collect(), Some(additional_vars)) } else { - (self.cargs_interner.get(instr.clbits).to_vec(), None) + (self.cargs_interner.get(instr.clbits()).to_vec(), None) } }; self.increment_op(op_name); - let qubits_id = instr.qubits; + let qubits_id = instr.qubits(); let new_node = self.dag.add_node(NodeType::Operation(instr)); // Put the new node in-between the previously "last" nodes on each wire @@ -5006,25 +5004,25 @@ impl DAGCircuit { /// another that was created from the first via /// [DAGCircuit::copy_empty_like]. fn push_front(&mut self, py: Python, inst: PackedInstruction) -> PyResult { - let op_name = inst.op.name(); + let op_name = inst.op().name(); let (all_cbits, vars): (Vec, Option>) = { if self.may_have_additional_wires(py, &inst) { let mut clbits: HashSet = - HashSet::from_iter(self.cargs_interner.get(inst.clbits).iter().copied()); + HashSet::from_iter(self.cargs_interner.get(inst.clbits()).iter().copied()); let (additional_clbits, additional_vars) = - self.additional_wires(py, inst.op.view(), inst.condition())?; + self.additional_wires(py, inst.op().view(), inst.condition())?; for clbit in additional_clbits { clbits.insert(clbit); } (clbits.into_iter().collect(), Some(additional_vars)) } else { - (self.cargs_interner.get(inst.clbits).to_vec(), None) + (self.cargs_interner.get(inst.clbits()).to_vec(), None) } }; self.increment_op(op_name); - let qubits_id = inst.qubits; + let qubits_id = inst.qubits(); let new_node = self.dag.add_node(NodeType::Operation(inst)); // Put the new node in-between the input map and the previously @@ -5149,15 +5147,13 @@ impl DAGCircuit { } else { OnceLock::new() }; - let packed_instruction = PackedInstruction { + let packed_instruction = PackedInstruction::new( op, - qubits: self.qargs_interner.insert(qargs), - clbits: self.cargs_interner.insert(cargs), - params: params.map(Box::new), + self.qargs_interner.insert(qargs), + self.cargs_interner.insert(cargs), + params, extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op, - }; + ); if front { self.push_front(py, packed_instruction) @@ -5169,8 +5165,8 @@ impl DAGCircuit { fn sort_key(&self, node: NodeIndex) -> SortKeyType { match &self.dag[node] { NodeType::Operation(packed) => ( - self.qargs_interner.get(packed.qubits), - self.cargs_interner.get(packed.clbits), + self.qargs_interner.get(packed.qubits()), + self.cargs_interner.get(packed.clbits()), ), NodeType::QubitIn(q) => (std::slice::from_ref(q), &[Clbit(u32::MAX)]), NodeType::QubitOut(_q) => (&[Qubit(u32::MAX)], &[Clbit(u32::MAX)]), @@ -5268,7 +5264,7 @@ impl DAGCircuit { if instr.condition().is_some() { return true; } - let OperationRef::Instruction(inst) = instr.op.view() else { + let OperationRef::Instruction(inst) = instr.op().view() else { return false; }; inst.control_flow() @@ -5530,7 +5526,7 @@ impl DAGCircuit { match self.dag.remove_node(index) { Some(NodeType::Operation(packed)) => { - let op_name = packed.op.name(); + let op_name = packed.op().name(); self.decrement_op(op_name); } _ => panic!("Must be called with valid operation node!"), @@ -5590,16 +5586,14 @@ impl DAGCircuit { .collect(), ); let params = (!op_node.instruction.params.is_empty()) - .then(|| Box::new(op_node.instruction.params.clone())); - let inst = PackedInstruction { - op: op_node.instruction.operation.clone(), + .then(|| op_node.instruction.params.clone()); + let inst = PackedInstruction::new( + op_node.instruction.operation.clone(), qubits, clbits, params, - extra_attrs: op_node.instruction.extra_attrs.clone(), - #[cfg(feature = "cache_pygates")] - py_op: op_node.instruction.py_op.clone(), - }; + op_node.instruction.extra_attrs.clone(), + ); NodeType::Operation(inst) } else { return Err(PyTypeError::new_err("Invalid type for DAGNode")); @@ -5629,20 +5623,24 @@ impl DAGCircuit { )? .into_any(), NodeType::Operation(packed) => { - let qubits = self.qargs_interner.get(packed.qubits); - let clbits = self.cargs_interner.get(packed.clbits); + let qubits = self.qargs_interner.get(packed.qubits()); + let clbits = self.cargs_interner.get(packed.clbits()); Py::new( py, ( DAGOpNode { instruction: CircuitInstruction { - operation: packed.op.clone(), + 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(), - params: packed.params_view().iter().cloned().collect(), - extra_attrs: packed.extra_attrs.clone(), + params: packed + .params_view() + .iter() + .map(|param| param.clone_ref(py)) + .collect(), + extra_attrs: packed.extra_attrs().clone(), #[cfg(feature = "cache_pygates")] py_op: packed.py_op.clone(), }, @@ -5681,7 +5679,7 @@ impl DAGCircuit { }); if !include_directives { Box::new(node_ops_iter.filter_map(|(index, node)| { - if !node.op.directive() { + if !node.op().directive() { Some(index) } else { None @@ -5697,7 +5695,7 @@ impl DAGCircuit { Box::new(self.op_nodes(false).filter(|index| { let weight = self.dag.node_weight(*index).expect("NodeIndex in graph"); if let NodeType::Operation(ref packed) = weight { - let qargs = self.qargs_interner.get(packed.qubits); + let qargs = self.qargs_interner.get(packed.qubits()); qargs.len() == 2 } else { false @@ -5733,9 +5731,9 @@ impl DAGCircuit { .node_references() .filter_map(move |(node, weight)| { if let NodeType::Operation(ref packed) = weight { - if !include_directives && packed.op.directive() { + if !include_directives && packed.op().directive() { None - } else if packed.op.py_op_is_instance(op).unwrap() { + } else if packed.op().py_op_is_instance(op).unwrap() { Some(node) } else { None @@ -5878,19 +5876,19 @@ impl DAGCircuit { if let NodeType::Operation(ref mut new_inst) = new_node { let new_qubit_indices: Vec = other .qargs_interner - .get(new_inst.qubits) + .get(new_inst.qubits()) .iter() .map(|old_qubit| qubit_map[old_qubit]) .collect(); let new_clbit_indices: Vec = other .cargs_interner - .get(new_inst.clbits) + .get(new_inst.clbits()) .iter() .map(|old_clbit| clbit_map[old_clbit]) .collect(); - new_inst.qubits = self.qargs_interner.insert_owned(new_qubit_indices); - new_inst.clbits = self.cargs_interner.insert_owned(new_clbit_indices); - self.increment_op(new_inst.op.name()); + *new_inst.qubits_mut() = self.qargs_interner.insert_owned(new_qubit_indices); + *new_inst.clbits_mut() = self.cargs_interner.insert_owned(new_clbit_indices); + self.increment_op(new_inst.op().name()); } let new_index = self.dag.add_node(new_node); out_map.insert(old_index, new_index); @@ -5900,7 +5898,7 @@ impl DAGCircuit { if out_map.is_empty() { match self.dag.remove_node(node) { Some(NodeType::Operation(packed)) => { - let op_name = packed.op.name(); + let op_name = packed.op().name(); self.decrement_op(op_name); } _ => unreachable!("Must be called with valid operation node!"), @@ -6009,7 +6007,7 @@ impl DAGCircuit { } // Remove node if let NodeType::Operation(inst) = &self.dag[node] { - self.decrement_op(inst.op.name().to_string().as_str()); + self.decrement_op(inst.op().name().to_string().as_str()); } self.dag.remove_node(node); Ok(out_map) @@ -6060,10 +6058,10 @@ impl DAGCircuit { fn check_op_addition(&self, py: Python, inst: &PackedInstruction) -> PyResult<()> { if let Some(condition) = inst.condition() { - self._check_condition(py, inst.op.name(), condition.bind(py))?; + self._check_condition(py, inst.op().name(), condition.bind(py))?; } - for b in self.qargs_interner.get(inst.qubits) { + for b in self.qargs_interner.get(inst.qubits()) { if self.qubit_io_map.len() - 1 < b.index() { return Err(DAGCircuitError::new_err(format!( "qubit {} not found in output map", @@ -6072,7 +6070,7 @@ impl DAGCircuit { } } - for b in self.cargs_interner.get(inst.clbits) { + for b in self.cargs_interner.get(inst.clbits()) { if !self.clbit_io_map.len() - 1 < b.index() { return Err(DAGCircuitError::new_err(format!( "clbit {} not found in output map", @@ -6082,7 +6080,7 @@ impl DAGCircuit { } if self.may_have_additional_wires(py, inst) { - let (clbits, vars) = self.additional_wires(py, inst.op.view(), inst.condition())?; + let (clbits, vars) = self.additional_wires(py, inst.op().view(), inst.condition())?; for b in clbits { if !self.clbit_io_map.len() - 1 < b.index() { return Err(DAGCircuitError::new_err(format!( @@ -6185,16 +6183,14 @@ impl DAGCircuit { self.increment_op(new_gate.0.name()); let old_node = &self.dag[old_index]; let inst = if let NodeType::Operation(old_node) = old_node { - PackedInstruction { - op: new_gate.0.into(), - qubits: old_node.qubits, - clbits: old_node.clbits, - params: (!new_gate.1.is_empty()) - .then(|| Box::new(new_gate.1.iter().map(|x| Param::Float(*x)).collect())), - extra_attrs: ExtraInstructionAttributes::default(), - #[cfg(feature = "cache_pygates")] - py_op: OnceLock::new(), - } + PackedInstruction::new( + new_gate.0.into(), + old_node.qubits(), + old_node.clbits(), + (!new_gate.1.is_empty()) + .then_some(new_gate.1.iter().map(|x| Param::Float(*x)).collect()), + ExtraInstructionAttributes::default(), + ) } else { panic!("This method only works if provided index is an op node"); }; @@ -6230,7 +6226,7 @@ impl DAGCircuit { for node in sequence { match self.dag.remove_node(*node) { Some(NodeType::Operation(packed)) => { - let op_name = packed.op.name(); + let op_name = packed.op().name(); self.decrement_op(op_name); } _ => panic!("Must be called with valid operation node!"), @@ -6283,15 +6279,13 @@ impl DAGCircuit { } OperationRef::Operation(op) => OnceLock::from(op.operation.clone_ref(py)), }; - let inst = PackedInstruction { - op: new_op.operation, - qubits: self.qargs_interner.insert_owned(qubits), - clbits: self.cargs_interner.get_default(), - params: (!new_op.params.is_empty()).then(|| Box::new(new_op.params)), - extra_attrs: new_op.extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op, - }; + let inst = PackedInstruction::new( + new_op.operation, + self.qargs_interner.insert_owned(qubits), + self.cargs_interner.get_default(), + (!new_op.params.is_empty()).then_some(new_op.params), + new_op.extra_attrs, + ); let new_index = self.dag.add_node(NodeType::Operation(inst)); self.dag.add_edge(source, new_index, weight.clone()); self.dag.add_edge(new_index, target, weight); @@ -6299,7 +6293,7 @@ impl DAGCircuit { match self.dag.remove_node(node) { Some(NodeType::Operation(packed)) => { - let op_name = packed.op.name(); + let op_name = packed.op().name(); self.decrement_op(op_name); } _ => panic!("Must be called with valid operation node"), @@ -6326,30 +6320,29 @@ impl DAGCircuit { pub fn has_calibration_for_index(&self, py: Python, node_index: NodeIndex) -> PyResult { let node = &self.dag[node_index]; if let NodeType::Operation(instruction) = node { - if !self.calibrations.contains_key(instruction.op.name()) { + if !self.calibrations.contains_key(instruction.op().name()) { return Ok(false); } - let params = match &instruction.params { - Some(params) => { - let mut out_params = Vec::new(); - for p in params.iter() { - if let Param::ParameterExpression(exp) = p { - let exp = exp.bind(py); - if !exp.getattr(intern!(py, "parameters"))?.is_truthy()? { - let as_py_float = exp.call_method0(intern!(py, "__float__"))?; - out_params.push(as_py_float.unbind()); - continue; - } + let params = if instruction.params_view().is_empty() { + PyTuple::empty_bound(py) + } else { + let mut out_params = Vec::new(); + for p in instruction.params_view().iter() { + if let Param::ParameterExpression(exp) = p { + let exp = exp.bind(py); + if !exp.getattr(intern!(py, "parameters"))?.is_truthy()? { + let as_py_float = exp.call_method0(intern!(py, "__float__"))?; + out_params.push(as_py_float.unbind()); + continue; } - out_params.push(p.to_object(py)); } - PyTuple::new_bound(py, out_params) + out_params.push(p.to_object(py)); } - None => PyTuple::empty_bound(py), + PyTuple::new_bound(py, out_params) }; - let qargs = self.qargs_interner.get(instruction.qubits); + let qargs = self.qargs_interner.get(instruction.qubits()); let qubits = PyTuple::new_bound(py, qargs.iter().map(|x| x.0)); - self.calibrations[instruction.op.name()] + self.calibrations[instruction.op().name()] .bind(py) .contains((qubits, params).to_object(py)) } else { @@ -6386,10 +6379,10 @@ impl DAGCircuit { let NodeType::Operation(node) = node else { continue; }; - if !node.op.control_flow() { + if !node.op().control_flow() { continue; } - let OperationRef::Instruction(inst) = node.op.view() else { + let OperationRef::Instruction(inst) = node.op().view() else { panic!("control flow op must be an instruction") }; let blocks = inst.instruction.bind(py).getattr("blocks")?; @@ -6434,19 +6427,19 @@ impl DAGCircuit { // Store new nodes to return let mut new_nodes = Vec::with_capacity(iter.size_hint().1.unwrap_or_default()); for instr in iter { - let op_name = instr.op.name(); + let op_name = instr.op().name(); let (all_cbits, vars): (Vec, Option>) = { if self.may_have_additional_wires(py, &instr) { let mut clbits: HashSet = - HashSet::from_iter(self.cargs_interner.get(instr.clbits).iter().copied()); + HashSet::from_iter(self.cargs_interner.get(instr.clbits()).iter().copied()); let (additional_clbits, additional_vars) = - self.additional_wires(py, instr.op.view(), instr.condition())?; + self.additional_wires(py, instr.op().view(), instr.condition())?; for clbit in additional_clbits { clbits.insert(clbit); } (clbits.into_iter().collect(), Some(additional_vars)) } else { - (self.cargs_interner.get(instr.clbits).to_vec(), None) + (self.cargs_interner.get(instr.clbits()).to_vec(), None) } }; @@ -6454,7 +6447,7 @@ impl DAGCircuit { self.increment_op(op_name); // Get the correct qubit indices - let qubits_id = instr.qubits; + let qubits_id = instr.qubits(); // Insert op-node to graph. let new_node = self.dag.add_node(NodeType::Operation(instr)); @@ -6696,7 +6689,7 @@ impl DAGCircuit { // Re-map the qubits let new_qargs = if let Some(qubit_mapping) = &qubit_map { let qargs = qc_data - .get_qargs(instr.qubits) + .get_qargs(instr.qubits()) .iter() .map(|bit| qubit_mapping[bit.index()]) .collect(); @@ -6704,12 +6697,12 @@ impl DAGCircuit { } else { new_dag .qargs_interner - .insert(qc_data.get_qargs(instr.qubits)) + .insert(qc_data.get_qargs(instr.qubits())) }; // Remap the clbits let new_cargs = if let Some(clbit_mapping) = &clbit_map { let qargs = qc_data - .get_cargs(instr.clbits) + .get_cargs(instr.clbits()) .iter() .map(|bit| clbit_mapping[bit.index()]) .collect(); @@ -6717,23 +6710,21 @@ impl DAGCircuit { } else { new_dag .cargs_interner - .insert(qc_data.get_cargs(instr.clbits)) + .insert(qc_data.get_cargs(instr.clbits())) }; // Copy the operations - Ok(PackedInstruction { - op: if copy_op { - instr.op.py_deepcopy(py, None)? + Ok(PackedInstruction::new( + if copy_op { + instr.op().py_deepcopy(py, None)? } else { - instr.op.clone() + instr.op().clone() }, - qubits: new_qargs, - clbits: new_cargs, - params: instr.params.clone(), - extra_attrs: instr.extra_attrs.clone(), - #[cfg(feature = "cache_pygates")] - py_op: OnceLock::new(), - }) + new_qargs, + new_cargs, + (!instr.params_view().is_empty()).then_some(instr.params_view().into()), + instr.extra_attrs().clone(), + )) }) .collect::>>()?; @@ -6780,9 +6771,9 @@ impl DAGCircuit { let weight = self.dag.node_weight(*nd); match weight { Some(NodeType::Operation(packed)) => { - block_op_names.push(packed.op.name().to_string()); - block_qargs.extend(self.qargs_interner.get(packed.qubits)); - block_cargs.extend(self.cargs_interner.get(packed.clbits)); + block_op_names.push(packed.op().name().to_string()); + block_qargs.extend(self.qargs_interner.get(packed.qubits())); + block_cargs.extend(self.cargs_interner.get(packed.clbits())); if let Some(condition) = packed.condition() { block_cargs.extend( @@ -6797,7 +6788,7 @@ impl DAGCircuit { } // Add classical bits from SwitchCaseOp, if applicable. - if let OperationRef::Instruction(op) = packed.op.view() { + if let OperationRef::Instruction(op) = packed.op().view() { if op.name() == "switch_case" { let op_bound = op.instruction.bind(py); let target = op_bound.getattr(intern!(py, "target"))?; @@ -6859,15 +6850,13 @@ impl DAGCircuit { let op_name = py_op.operation.name().to_string(); let qubits = self.qargs_interner.insert_owned(block_qargs); let clbits = self.cargs_interner.insert_owned(block_cargs); - let weight = NodeType::Operation(PackedInstruction { - op: py_op.operation, + let weight = NodeType::Operation(PackedInstruction::new( + py_op.operation, qubits, clbits, - params: (!py_op.params.is_empty()).then(|| Box::new(py_op.params)), - extra_attrs: py_op.extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op: op.unbind().into(), - }); + (!py_op.params.is_empty()).then_some(py_op.params), + py_op.extra_attrs, + )); let new_node = self .dag @@ -6895,7 +6884,7 @@ impl DAGCircuit { ) -> PyResult<()> { // Extract information from node that is going to be replaced let old_packed = self.dag[node_index].unwrap_operation(); - let op_name = old_packed.op.name().to_string(); + let op_name = old_packed.op().name().to_string(); // Extract information from new op let new_op = op.extract::()?; let current_wires: HashSet = self @@ -6905,12 +6894,12 @@ impl DAGCircuit { .collect(); let mut new_wires: HashSet = self .qargs_interner - .get(old_packed.qubits) + .get(old_packed.qubits()) .iter() .map(|x| Wire::Qubit(*x)) .chain( self.cargs_interner - .get(old_packed.clbits) + .get(old_packed.clbits()) .iter() .map(|x| Wire::Clbit(*x)), ) @@ -6924,13 +6913,13 @@ impl DAGCircuit { .map(|x| Wire::Var(self.vars.find(x.bind(py)).unwrap())), ); - if old_packed.op.num_qubits() != new_op.operation.num_qubits() - || old_packed.op.num_clbits() != new_op.operation.num_clbits() + if old_packed.op().num_qubits() != new_op.operation.num_qubits() + || old_packed.op().num_clbits() != new_op.operation.num_clbits() { return Err(DAGCircuitError::new_err( format!( "Cannot replace node of width ({} qubits, {} clbits) with operation of mismatched width ({} qubits, {} clbits)", - old_packed.op.num_qubits(), old_packed.op.num_clbits(), new_op.operation.num_qubits(), new_op.operation.num_clbits() + old_packed.op().num_qubits(), old_packed.op().num_clbits(), new_op.operation.num_qubits(), new_op.operation.num_clbits() ))); } @@ -6939,7 +6928,8 @@ impl DAGCircuit { let mut extra_attrs = new_op.extra_attrs.clone(); // If either operation is a control-flow operation, propagate_condition is ignored - if propagate_condition && !(old_packed.op.control_flow() || new_op.operation.control_flow()) + if propagate_condition + && !(old_packed.op().control_flow() || new_op.operation.control_flow()) { // if new_op has a condition, the condition can't be propagated from the old node if new_op.extra_attrs.condition().is_some() { @@ -6983,19 +6973,17 @@ impl DAGCircuit { // The new wires must be a non-strict subset of the current wires; if they add new // wires, we'd not know where to cut the existing wire to insert the new dependency. return Err(DAGCircuitError::new_err(format!( - "New operation '{:?}' does not span the same wires as the old node '{:?}'. New wires: {:?}, old_wires: {:?}.", op.str(), old_packed.op.view(), new_wires, current_wires + "New operation '{:?}' does not span the same wires as the old node '{:?}'. New wires: {:?}, old_wires: {:?}.", op.str(), old_packed.op().view(), new_wires, current_wires ))); } let new_op_name = new_op.operation.name().to_string(); - let new_weight = NodeType::Operation(PackedInstruction { - op: new_op.operation, - qubits: old_packed.qubits, - clbits: old_packed.clbits, - params: (!new_op.params.is_empty()).then(|| new_op.params.into()), + let new_weight = NodeType::Operation(PackedInstruction::new( + new_op.operation, + old_packed.qubits(), + old_packed.clbits(), + (!new_op.params.is_empty()).then_some(new_op.params), extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op: py_op_cache.map(OnceLock::from).unwrap_or_default(), - }); + )); if let Some(weight) = self.dag.node_weight_mut(node_index) { *weight = new_weight; } diff --git a/crates/circuit/src/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs index 4da706280336..a04eea2faf70 100644 --- a/crates/circuit/src/packed_instruction.rs +++ b/crates/circuit/src/packed_instruction.rs @@ -493,15 +493,15 @@ impl Drop for PackedOperation { /// /// A `PackedInstruction` in general cannot be safely mutated outside the context of its /// `CircuitData`, because the majority of the data is not actually stored here. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct PackedInstruction { - pub op: PackedOperation, + op: PackedOperation, /// The index under which the interner has stored `qubits`. - pub qubits: Interned<[Qubit]>, + qubits: Interned<[Qubit]>, /// The index under which the interner has stored `clbits`. - pub clbits: Interned<[Clbit]>, - pub params: Option>>, - pub extra_attrs: ExtraInstructionAttributes, + clbits: Interned<[Clbit]>, + params: Option>>, + extra_attrs: ExtraInstructionAttributes, #[cfg(feature = "cache_pygates")] /// This is hidden in a `OnceLock` because it's just an on-demand cache; we don't create this @@ -518,6 +518,83 @@ pub struct PackedInstruction { } impl PackedInstruction { + /// Creates a new instance of #[PackedInstruction] + pub fn new( + op: PackedOperation, + qubits: Interned<[Qubit]>, + clbits: Interned<[Clbit]>, + params: Option>, + extra_attrs: ExtraInstructionAttributes, + ) -> Self { + Self { + op, + qubits, + clbits, + params: params.map(Box::new), + extra_attrs, + #[cfg(feature = "cache_pygates")] + py_op: OnceLock::new(), + } + } + + // Getters + + /// Retrieves an immutable reference to the instruction's underlying operation. + pub fn op(&self) -> &PackedOperation { + &self.op + } + + /// Retrieves an immutable reference to the index under which the interner has stored `qubits`. + pub fn qubits(&self) -> Interned<[Qubit]> { + self.qubits + } + + /// Retrieves an immutable reference to the index under which the interner has stored `clbits`. + pub fn clbits(&self) -> Interned<[Clbit]> { + self.clbits + } + + pub fn extra_attrs(&self) -> &ExtraInstructionAttributes { + &self.extra_attrs + } + + // Setters + + /// Retrieves an immutable reference to the instruction's underlying operation. + pub fn op_mut(&mut self) -> &mut PackedOperation { + #[cfg(feature = "cache_pygates")] + { + self.py_op.take(); + } + &mut self.op + } + + /// Retrieves an immutable reference to the index under which the interner has stored `qubits`. + pub fn qubits_mut(&mut self) -> &mut Interned<[Qubit]> { + #[cfg(feature = "cache_pygates")] + { + self.py_op.take(); + } + &mut self.qubits + } + + /// Retrieves an immutable reference to the index under which the interner has stored `clbits`. + pub fn clbits_mut(&mut self) -> &mut Interned<[Clbit]> { + #[cfg(feature = "cache_pygates")] + { + self.py_op.take(); + } + &mut self.clbits + } + + pub fn extra_attrs_mut(&mut self) -> &mut ExtraInstructionAttributes { + #[cfg(feature = "cache_pygates")] + { + self.py_op.take(); + } + &mut self.extra_attrs + } + /// Access the standard gate in this `PackedInstruction`, if it is one. If the instruction /// refers to a Python-space object, `None` is returned. #[inline] @@ -536,11 +613,29 @@ impl PackedInstruction { /// Get a mutable slice view onto the contained parameters. #[inline] - pub fn params_mut(&mut self) -> &mut [Param] { - self.params + pub fn params_view_mut(&mut self) -> &mut [Param] { + let params = self + .params .as_deref_mut() .map(SmallVec::as_mut_slice) - .unwrap_or(&mut []) + .unwrap_or(&mut []); + #[cfg(feature = "cache_pygates")] + { + // Delete the old cache to ensure changes to parameters regenerate the cached instruction. + self.py_op.take(); + } + params + } + + /// Get a mutable reference of the contained parameters. + #[inline] + pub fn params_mut(&mut self) -> &mut Option>> { + #[cfg(feature = "cache_pygates")] + { + // Delete the old cache to ensure changes to parameters regenerate the cached instruction. + self.py_op.take(); + } + &mut self.params } /// Does this instruction contain any compile-time symbolic `ParameterExpression`s? @@ -628,3 +723,23 @@ impl PackedInstruction { } } } + +// When cloning a `PackedInstruction` we will need to delete the old cached +// `Gate` to avoid having duplicated references to the same python object. +impl Clone for PackedInstruction { + fn clone(&self) -> Self { + Self { + op: self.op.clone(), + qubits: self.qubits, + clbits: self.clbits, + params: self.params.clone(), + extra_attrs: self.extra_attrs.clone(), + #[cfg(feature = "cache_pygates")] + py_op: OnceLock::new(), + } + } + + fn clone_from(&mut self, source: &Self) { + *self = source.clone() + } +} From b337cf3efba01561dee5d74902cdeca6d557e8b7 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:13:08 -0500 Subject: [PATCH 2/6] Fix: Do not discard cache when modifying qubits and clbits --- crates/circuit/src/packed_instruction.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/circuit/src/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs index a04eea2faf70..d52cbc1ac0c0 100644 --- a/crates/circuit/src/packed_instruction.rs +++ b/crates/circuit/src/packed_instruction.rs @@ -571,19 +571,11 @@ impl PackedInstruction { /// Retrieves an immutable reference to the index under which the interner has stored `qubits`. pub fn qubits_mut(&mut self) -> &mut Interned<[Qubit]> { - #[cfg(feature = "cache_pygates")] - { - self.py_op.take(); - } &mut self.qubits } /// Retrieves an immutable reference to the index under which the interner has stored `clbits`. pub fn clbits_mut(&mut self) -> &mut Interned<[Clbit]> { - #[cfg(feature = "cache_pygates")] - { - self.py_op.take(); - } &mut self.clbits } From 473ca0396f6a2b37c869f09dc77d5b6020d02abf Mon Sep 17 00:00:00 2001 From: Raynel Sanchez Date: Tue, 10 Dec 2024 12:45:53 -0500 Subject: [PATCH 3/6] Fix: Make `py_op` private. - Add views for `py_op`. - Remove stale saching being done in `apply_operation_*` in `DAGCircuit`. - Adapt the code to new changes. - Add missing docstring --- .../src/barrier_before_final_measurement.rs | 31 +++-------- .../basis_translator/compose_transforms.rs | 2 - .../src/basis/basis_translator/mod.rs | 12 ----- crates/accelerate/src/elide_permutations.rs | 2 - crates/accelerate/src/gate_direction.rs | 4 +- crates/accelerate/src/twirling.rs | 2 - crates/accelerate/src/unitary_synthesis.rs | 4 -- crates/circuit/src/circuit_data.rs | 13 ++--- crates/circuit/src/converters.rs | 3 -- crates/circuit/src/dag_circuit.rs | 51 ++----------------- crates/circuit/src/packed_instruction.rs | 16 +++++- 11 files changed, 32 insertions(+), 108 deletions(-) diff --git a/crates/accelerate/src/barrier_before_final_measurement.rs b/crates/accelerate/src/barrier_before_final_measurement.rs index c3c9ee7809b3..36e3fd59b691 100644 --- a/crates/accelerate/src/barrier_before_final_measurement.rs +++ b/crates/accelerate/src/barrier_before_final_measurement.rs @@ -84,29 +84,14 @@ pub fn barrier_before_final_measurements( instruction: new_barrier.unbind(), }; let qargs: Vec = (0..dag.num_qubits() as u32).map(Qubit).collect(); - #[cfg(feature = "cache_pygates")] - { - dag.apply_operation_back( - py, - PackedOperation::from_instruction(Box::new(new_barrier_py_inst)), - qargs.as_slice(), - &[], - None, - ExtraInstructionAttributes::new(label, None, None, None), - Some(new_barrier.unbind()), - )?; - } - #[cfg(not(feature = "cache_pygates"))] - { - dag.apply_operation_back( - py, - PackedOperation::from_instruction(Box::new(new_barrier_py_inst)), - qargs.as_slice(), - &[], - None, - ExtraInstructionAttributes::new(label, None, None, None), - )?; - } + dag.apply_operation_back( + py, + PackedOperation::from_instruction(Box::new(new_barrier_py_inst)), + qargs.as_slice(), + &[], + None, + ExtraInstructionAttributes::new(label, None, None, None), + )?; for inst in final_packed_ops { dag.push_back(py, inst)?; } diff --git a/crates/accelerate/src/basis/basis_translator/compose_transforms.rs b/crates/accelerate/src/basis/basis_translator/compose_transforms.rs index fa07b224de08..79efdac90f6c 100644 --- a/crates/accelerate/src/basis/basis_translator/compose_transforms.rs +++ b/crates/accelerate/src/basis/basis_translator/compose_transforms.rs @@ -74,8 +74,6 @@ pub(super) fn compose_transforms<'a>( Some(gate_obj.params) }, gate_obj.extra_attrs, - #[cfg(feature = "cache_pygates")] - Some(gate.into()), )?; mapped_instructions.insert((gate_name, gate_num_qubits), (placeholder_params, dag)); diff --git a/crates/accelerate/src/basis/basis_translator/mod.rs b/crates/accelerate/src/basis/basis_translator/mod.rs index f10b0adbdeb4..c36aa73a7486 100644 --- a/crates/accelerate/src/basis/basis_translator/mod.rs +++ b/crates/accelerate/src/basis/basis_translator/mod.rs @@ -481,8 +481,6 @@ fn apply_translation( node_carg, (!new_op.params.is_empty()).then_some(new_op.params), new_op.extra_attrs, - #[cfg(feature = "cache_pygates")] - None, )?; } else { out_dag.apply_operation_back( @@ -498,8 +496,6 @@ fn apply_translation( .collect(), ), node_obj.extra_attrs().clone(), - #[cfg(feature = "cache_pygates")] - None, )?; } continue; @@ -523,8 +519,6 @@ fn apply_translation( .collect(), ), node_obj.extra_attrs().clone(), - #[cfg(feature = "cache_pygates")] - None, )?; continue; } @@ -543,8 +537,6 @@ fn apply_translation( .collect(), ), node_obj.extra_attrs().clone(), - #[cfg(feature = "cache_pygates")] - None, )?; continue; } @@ -642,8 +634,6 @@ fn replace_node( &new_clbits, (!new_params.is_empty()).then_some(new_params), new_extra_props, - #[cfg(feature = "cache_pygates")] - None, )?; } dag.add_global_phase(py, target_dag.global_phase())?; @@ -742,8 +732,6 @@ fn replace_node( &new_clbits, (!new_params.is_empty()).then_some(new_params), inner_node.extra_attrs().clone(), - #[cfg(feature = "cache_pygates")] - None, )?; } diff --git a/crates/accelerate/src/elide_permutations.rs b/crates/accelerate/src/elide_permutations.rs index 511fb54761b1..f74857d02db5 100644 --- a/crates/accelerate/src/elide_permutations.rs +++ b/crates/accelerate/src/elide_permutations.rs @@ -89,8 +89,6 @@ fn run(py: Python, dag: &mut DAGCircuit) -> PyResult { py_inst @@ -3351,7 +3347,7 @@ def _format(operand): node.instruction.extra_attrs = new_weight.extra_attrs().clone(); #[cfg(feature = "cache_pygates")] { - node.instruction.py_op = new_weight.py_op.clone(); + node.instruction.py_op = new_weight.py_op().clone(); } Ok(node.into_py(py)) } else { @@ -5065,19 +5061,8 @@ impl DAGCircuit { cargs: &[Clbit], params: Option>, extra_attrs: ExtraInstructionAttributes, - #[cfg(feature = "cache_pygates")] py_op: Option, ) -> PyResult { - self.inner_apply_op( - py, - op, - qargs, - cargs, - params, - extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op, - false, - ) + self.inner_apply_op(py, op, qargs, cargs, params, extra_attrs, false) } /// Apply a [PackedOperation] to the front of the circuit. @@ -5089,19 +5074,8 @@ impl DAGCircuit { cargs: &[Clbit], params: Option>, extra_attrs: ExtraInstructionAttributes, - #[cfg(feature = "cache_pygates")] py_op: Option, ) -> PyResult { - self.inner_apply_op( - py, - op, - qargs, - cargs, - params, - extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op, - true, - ) + self.inner_apply_op(py, op, qargs, cargs, params, extra_attrs, true) } #[inline] @@ -5114,7 +5088,6 @@ impl DAGCircuit { cargs: &[Clbit], params: Option>, extra_attrs: ExtraInstructionAttributes, - #[cfg(feature = "cache_pygates")] py_op: Option, front: bool, ) -> PyResult { // Check that all qargs are within an acceptable range @@ -5140,13 +5113,6 @@ impl DAGCircuit { } Ok(()) })?; - - #[cfg(feature = "cache_pygates")] - let py_op = if let Some(py_op) = py_op { - py_op.into() - } else { - OnceLock::new() - }; let packed_instruction = PackedInstruction::new( op, self.qargs_interner.insert(qargs), @@ -5642,7 +5608,7 @@ impl DAGCircuit { .collect(), extra_attrs: packed.extra_attrs().clone(), #[cfg(feature = "cache_pygates")] - py_op: packed.py_op.clone(), + py_op: packed.py_op().clone(), }, sort_key: format!("{:?}", self.sort_key(id)).into_py(py), }, @@ -6923,9 +6889,6 @@ impl DAGCircuit { ))); } - #[cfg(feature = "cache_pygates")] - let mut py_op_cache = Some(op.clone().unbind()); - let mut extra_attrs = new_op.extra_attrs.clone(); // If either operation is a control-flow operation, propagate_condition is ignored if propagate_condition @@ -6963,10 +6926,6 @@ impl DAGCircuit { old_condition.downcast_bound::(py)?, )?; } - #[cfg(feature = "cache_pygates")] - { - py_op_cache = None; - } } }; if new_wires != current_wires { diff --git a/crates/circuit/src/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs index d52cbc1ac0c0..96d6d6fb250b 100644 --- a/crates/circuit/src/packed_instruction.rs +++ b/crates/circuit/src/packed_instruction.rs @@ -514,7 +514,7 @@ pub struct PackedInstruction { /// requires the GIL to even `get` (of course!), which makes implementing `Clone` hard for us. /// We can revisit once we're on PyO3 0.22+ and have been able to disable its `py-clone` /// feature. - pub py_op: OnceLock>, + py_op: OnceLock>, } impl PackedInstruction { @@ -554,10 +554,17 @@ impl PackedInstruction { self.clbits } + /// Retrieves an immutable reference to the extra_attributes of this instruction. pub fn extra_attrs(&self) -> &ExtraInstructionAttributes { &self.extra_attrs } + /// Retrieves the cached py_gate immutably. + #[cfg(feature = "cache_pygates")] + pub fn py_op(&self) -> &OnceLock { + &self.py_op + } + // Setters /// Retrieves an immutable reference to the instruction's underlying operation. @@ -579,6 +586,7 @@ impl PackedInstruction { &mut self.clbits } + /// Retrieves a mutable reference to the extra_attributes of this instruction. pub fn extra_attrs_mut(&mut self) -> &mut ExtraInstructionAttributes { #[cfg(feature = "cache_pygates")] { @@ -587,6 +595,12 @@ impl PackedInstruction { &mut self.extra_attrs } + /// Retrieves the cached py_gate immutably. + #[cfg(feature = "cache_pygates")] + pub fn py_op_mut(&mut self) -> &mut OnceLock { + &mut self.py_op + } + /// Access the standard gate in this `PackedInstruction`, if it is one. If the instruction /// refers to a Python-space object, `None` is returned. #[inline] From 84def52097b9fafc54600ac47ed68531d950cbcb Mon Sep 17 00:00:00 2001 From: Raynel Sanchez Date: Tue, 10 Dec 2024 14:20:38 -0500 Subject: [PATCH 4/6] Docs: Add release note --- .../notes/fix-packed-instruction-f2620d5aff61269d.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 releasenotes/notes/fix-packed-instruction-f2620d5aff61269d.yaml diff --git a/releasenotes/notes/fix-packed-instruction-f2620d5aff61269d.yaml b/releasenotes/notes/fix-packed-instruction-f2620d5aff61269d.yaml new file mode 100644 index 000000000000..cce8c8e24df5 --- /dev/null +++ b/releasenotes/notes/fix-packed-instruction-f2620d5aff61269d.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + ``PackedInstruction``, the principal representation of an instance of :class:`.CircuitInstruction` + in Rust, has been re-organized to discard its cached instance of a python :class:`.Gate` whenever its + operation, parameters, or extra attributes are modified. This results in ``PackedInstruction`` no + no longer having shared references between them. From 7a6f5f178a98716d884cc06afdea39d30d6f7363 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:29:04 -0500 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Kevin Hartman --- crates/circuit/src/packed_instruction.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/circuit/src/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs index 96d6d6fb250b..1181139a206d 100644 --- a/crates/circuit/src/packed_instruction.rs +++ b/crates/circuit/src/packed_instruction.rs @@ -567,7 +567,7 @@ impl PackedInstruction { // Setters - /// Retrieves an immutable reference to the instruction's underlying operation. + /// Retrieves a mutable reference to the instruction's underlying operation. pub fn op_mut(&mut self) -> &mut PackedOperation { #[cfg(feature = "cache_pygates")] { @@ -576,7 +576,7 @@ impl PackedInstruction { &mut self.op } - /// Retrieves an immutable reference to the index under which the interner has stored `qubits`. + /// Retrieves a mutable reference to the index under which the interner has stored `qubits`. pub fn qubits_mut(&mut self) -> &mut Interned<[Qubit]> { &mut self.qubits } @@ -595,7 +595,7 @@ impl PackedInstruction { &mut self.extra_attrs } - /// Retrieves the cached py_gate immutably. + /// Retrieves the cached py_gate mutably. #[cfg(feature = "cache_pygates")] pub fn py_op_mut(&mut self) -> &mut OnceLock { &mut self.py_op From 64b164409bddcde5b6b25ca6a3e5587b0178e2b1 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:38:46 -0500 Subject: [PATCH 6/6] Fix: Docstrings and "no no" in release note --- crates/circuit/src/packed_instruction.rs | 8 ++------ .../notes/fix-packed-instruction-f2620d5aff61269d.yaml | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/crates/circuit/src/packed_instruction.rs b/crates/circuit/src/packed_instruction.rs index 1181139a206d..934f0b0a4b63 100644 --- a/crates/circuit/src/packed_instruction.rs +++ b/crates/circuit/src/packed_instruction.rs @@ -537,8 +537,6 @@ impl PackedInstruction { } } - // Getters - /// Retrieves an immutable reference to the instruction's underlying operation. pub fn op(&self) -> &PackedOperation { &self.op @@ -565,8 +563,6 @@ impl PackedInstruction { &self.py_op } - // Setters - /// Retrieves a mutable reference to the instruction's underlying operation. pub fn op_mut(&mut self) -> &mut PackedOperation { #[cfg(feature = "cache_pygates")] @@ -581,7 +577,7 @@ impl PackedInstruction { &mut self.qubits } - /// Retrieves an immutable reference to the index under which the interner has stored `clbits`. + /// Retrieves a mutable reference to the index under which the interner has stored `clbits`. pub fn clbits_mut(&mut self) -> &mut Interned<[Clbit]> { &mut self.clbits } @@ -731,7 +727,7 @@ impl PackedInstruction { } // When cloning a `PackedInstruction` we will need to delete the old cached -// `Gate` to avoid having duplicated references to the same python object. +// operation to avoid having duplicated references to the same python object. impl Clone for PackedInstruction { fn clone(&self) -> Self { Self { diff --git a/releasenotes/notes/fix-packed-instruction-f2620d5aff61269d.yaml b/releasenotes/notes/fix-packed-instruction-f2620d5aff61269d.yaml index cce8c8e24df5..d5f8ac793544 100644 --- a/releasenotes/notes/fix-packed-instruction-f2620d5aff61269d.yaml +++ b/releasenotes/notes/fix-packed-instruction-f2620d5aff61269d.yaml @@ -4,4 +4,4 @@ fixes: ``PackedInstruction``, the principal representation of an instance of :class:`.CircuitInstruction` in Rust, has been re-organized to discard its cached instance of a python :class:`.Gate` whenever its operation, parameters, or extra attributes are modified. This results in ``PackedInstruction`` no - no longer having shared references between them. + longer having shared references between them.