diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 75a2f7993f86..aa89c0d4a735 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -13,7 +13,9 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::upper_case_acronyms)] -use hashbrown::HashMap; +use ahash::RandomState; +use hashbrown::{HashMap, HashSet}; +use indexmap::IndexSet; use num_complex::{Complex64, ComplexFloat}; use smallvec::{smallvec, SmallVec}; use std::cmp::Ordering; @@ -29,14 +31,19 @@ use pyo3::Python; use ndarray::prelude::*; use numpy::PyReadonlyArray2; use pyo3::pybacked::PyBackedStr; +use rustworkx_core::petgraph::stable_graph::NodeIndex; use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType}; use qiskit_circuit::dag_node::DAGOpNode; use qiskit_circuit::operations::{Operation, Param, StandardGate}; use qiskit_circuit::slice::{PySequenceIndex, SequenceIndex}; use qiskit_circuit::util::c64; use qiskit_circuit::Qubit; +use crate::nlayout::PhysicalQubit; +use crate::target_transpiler::Target; + pub const ANGLE_ZERO_EPSILON: f64 = 1e-12; #[pyclass(module = "qiskit._accelerate.euler_one_qubit_decomposer")] @@ -69,6 +76,7 @@ impl OneQubitGateErrorMap { } } +#[derive(Debug)] #[pyclass(sequence)] pub struct OneQubitGateSequence { pub gates: Vec<(StandardGate, SmallVec<[f64; 3]>)>, @@ -571,7 +579,7 @@ pub fn generate_circuit( Ok(res) } -#[derive(Clone, Debug, Copy)] +#[derive(Clone, Debug, Copy, Eq, Hash, PartialEq)] #[pyclass(module = "qiskit._accelerate.euler_one_qubit_decomposer")] pub enum EulerBasis { U321, @@ -684,24 +692,6 @@ fn compare_error_fn( } } -fn compute_error( - gates: &[(StandardGate, SmallVec<[f64; 3]>)], - error_map: Option<&OneQubitGateErrorMap>, - qubit: usize, -) -> (f64, usize) { - match error_map { - Some(err_map) => { - let num_gates = gates.len(); - let gate_fidelities: f64 = gates - .iter() - .map(|gate| 1. - err_map.error_map[qubit].get(gate.0.name()).unwrap_or(&0.)) - .product(); - (1. - gate_fidelities, num_gates) - } - None => (gates.len() as f64, gates.len()), - } -} - fn compute_error_term(gate: &str, error_map: &OneQubitGateErrorMap, qubit: usize) -> f64 { 1. - error_map.error_map[qubit].get(gate).unwrap_or(&0.) } @@ -724,15 +714,6 @@ fn compute_error_str( } } -#[pyfunction] -pub fn compute_error_one_qubit_sequence( - circuit: &OneQubitGateSequence, - qubit: usize, - error_map: Option<&OneQubitGateErrorMap>, -) -> (f64, usize) { - compute_error(&circuit.gates, error_map, qubit) -} - #[pyfunction] pub fn compute_error_list( circuit: Vec>, @@ -965,72 +946,231 @@ pub fn params_zxz(unitary: PyReadonlyArray2) -> [f64; 4] { params_zxz_inner(mat) } -type OptimizeDecompositionReturn = Option<((f64, usize), (f64, usize), OneQubitGateSequence)>; +fn compute_error_term_from_target(gate: &str, target: &Target, qubit: PhysicalQubit) -> f64 { + 1. - target.get_error(gate, &[qubit]).unwrap_or(0.) +} + +fn compute_error_from_target_one_qubit_sequence( + circuit: &OneQubitGateSequence, + qubit: PhysicalQubit, + target: Option<&Target>, +) -> (f64, usize) { + match target { + Some(target) => { + let num_gates = circuit.gates.len(); + let gate_fidelities: f64 = circuit + .gates + .iter() + .map(|gate| compute_error_term_from_target(gate.0.name(), target, qubit)) + .product(); + (1. - gate_fidelities, num_gates) + } + None => (circuit.gates.len() as f64, circuit.gates.len()), + } +} #[pyfunction] -pub fn optimize_1q_gates_decomposition( - runs: Vec>>, - qubits: Vec, - bases: Vec>, - simplify: bool, - error_map: Option<&OneQubitGateErrorMap>, - atol: Option, -) -> Vec { - runs.iter() - .enumerate() - .map(|(index, raw_run)| -> OptimizeDecompositionReturn { - let mut error = match error_map { - Some(_) => 1., - None => raw_run.len() as f64, +#[pyo3(signature = (dag, *, target=None, basis_gates=None, global_decomposers=None))] +pub(crate) fn optimize_1q_gates_decomposition( + py: Python, + dag: &mut DAGCircuit, + target: Option<&Target>, + basis_gates: Option>, + global_decomposers: Option>, +) -> PyResult<()> { + let runs: Vec> = dag.collect_1q_runs().unwrap().collect(); + let dag_qubits = dag.num_qubits(); + let mut target_basis_per_qubit: Vec>> = + vec![None; dag_qubits]; + let mut basis_gates_per_qubit: Vec>> = vec![None; dag_qubits]; + for raw_run in runs { + let mut error = match target { + Some(_) => 1., + None => raw_run.len() as f64, + }; + let qubit: PhysicalQubit = if let NodeType::Operation(inst) = &dag.dag[raw_run[0]] { + dag.get_qubits(inst.qubits)[0].into() + } else { + unreachable!("nodes in runs will always be op nodes") + }; + if !dag.calibrations_empty() { + let mut has_calibration = false; + for node in &raw_run { + if dag.has_calibration_for_index(py, *node)? { + has_calibration = true; + break; + } + } + if has_calibration { + continue; + } + } + if basis_gates_per_qubit[qubit.0 as usize].is_none() { + let basis_gates = match target { + Some(target) => Some( + target + .operation_names_for_qargs(Some(&smallvec![qubit])) + .unwrap(), + ), + None => { + let basis = basis_gates.as_ref(); + basis.map(|basis| basis.iter().map(|x| x.as_str()).collect()) + } }; - let qubit = qubits[index]; - let operator = &raw_run - .iter() - .map(|node| { - if let Some(err_map) = error_map { - error *= - compute_error_term(node.instruction.operation.name(), err_map, qubit) - } - node.instruction - .operation - .matrix(&node.instruction.params) - .expect("No matrix defined for operation") - }) - .fold( - [ - [Complex64::new(1., 0.), Complex64::new(0., 0.)], - [Complex64::new(0., 0.), Complex64::new(1., 0.)], - ], - |mut operator, node| { - matmul_1q(&mut operator, node); - operator + basis_gates_per_qubit[qubit.0 as usize] = basis_gates; + } + let basis_gates = &basis_gates_per_qubit[qubit.0 as usize].as_ref(); + + if target_basis_per_qubit[qubit.0 as usize].is_none() { + let mut target_basis_set: IndexSet = match target { + Some(_target) => EULER_BASIS_MAP + .iter() + .enumerate() + .filter_map(|(idx, gates)| { + if !gates + .iter() + .all(|gate| basis_gates.as_ref().unwrap().contains(gate)) + { + return None; + } + let basis = EULER_BASIS_NAMES[idx]; + Some(basis) + }) + .collect(), + None => match &global_decomposers { + Some(bases) => bases + .iter() + .map(|basis| EulerBasis::__new__(basis).unwrap()) + .collect(), + None => match basis_gates { + Some(gates) => EULER_BASIS_MAP + .iter() + .enumerate() + .filter_map(|(idx, basis_gates)| { + if !gates.iter().all(|gate| basis_gates.as_ref().contains(gate)) { + return None; + } + let basis = EULER_BASIS_NAMES[idx]; + Some(basis) + }) + .collect(), + None => EULER_BASIS_NAMES.iter().copied().collect(), }, - ); - let old_error = if error_map.is_some() { - (1. - error, raw_run.len()) - } else { - (error, raw_run.len()) + }, }; - let target_basis_vec: Vec = bases[index] - .iter() - .map(|basis| EulerBasis::__new__(basis).unwrap()) - .collect(); - unitary_to_gate_sequence_inner( - aview2(operator), - &target_basis_vec, - qubit, - error_map, - simplify, - atol, - ) - .map(|out_seq| { - let new_error = compute_error_one_qubit_sequence(&out_seq, qubit, error_map); - (old_error, new_error, out_seq) + if target_basis_set.contains(&EulerBasis::U3) + && target_basis_set.contains(&EulerBasis::U321) + { + target_basis_set.swap_remove(&EulerBasis::U3); + } + if target_basis_set.contains(&EulerBasis::ZSX) + && target_basis_set.contains(&EulerBasis::ZSXX) + { + target_basis_set.swap_remove(&EulerBasis::ZSX); + } + target_basis_per_qubit[qubit.0 as usize] = Some(target_basis_set); + } + let target_basis_set = target_basis_per_qubit[qubit.0 as usize].as_ref().unwrap(); + let target_basis_vec: Vec = target_basis_set.iter().copied().collect(); + let operator = raw_run + .iter() + .map(|node_index| { + 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); + } + inst.op.matrix(inst.params_view()).unwrap() + } else { + unreachable!("Can only have op nodes here") + } }) - }) - .collect() + .fold( + [ + [Complex64::new(1., 0.), Complex64::new(0., 0.)], + [Complex64::new(0., 0.), Complex64::new(1., 0.)], + ], + |mut operator, node| { + matmul_1q(&mut operator, node); + operator + }, + ); + + let old_error = if target.is_some() { + (1. - error, raw_run.len()) + } else { + (error, raw_run.len()) + }; + let sequence = unitary_to_gate_sequence_inner( + aview2(&operator), + &target_basis_vec, + qubit.0 as usize, + None, + true, + None, + ); + let sequence = match sequence { + Some(seq) => seq, + None => continue, + }; + let new_error = compute_error_from_target_one_qubit_sequence(&sequence, qubit, target); + + let mut outside_basis = false; + 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()) { + outside_basis = true; + break; + } + } + } + } else { + outside_basis = false; + } + if outside_basis + || new_error < old_error + || new_error.0.abs() < 1e-9 && old_error.0.abs() >= 1e-9 + { + for gate in sequence.gates { + dag.insert_1q_on_incoming_qubit((gate.0, &gate.1), raw_run[0]); + } + dag.add_global_phase(py, &Param::Float(sequence.global_phase))?; + dag.remove_1q_sequence(&raw_run); + } + } + Ok(()) } +static EULER_BASIS_MAP: [&[&str]; 12] = [ + &["u3"], + &["u3", "u2", "u1"], + &["u"], + &["p", "sx"], + &["u1", "rx"], + &["r"], + &["rz", "ry"], + &["rz", "rx"], + &["rz", "rx"], + &["rx", "ry"], + &["rz", "sx", "x"], + &["rz", "sx"], +]; +static EULER_BASIS_NAMES: [EulerBasis; 12] = [ + EulerBasis::U3, + EulerBasis::U321, + EulerBasis::U, + EulerBasis::PSX, + EulerBasis::U1X, + EulerBasis::RR, + EulerBasis::ZYZ, + EulerBasis::ZXZ, + EulerBasis::XZX, + EulerBasis::XYX, + EulerBasis::ZSXX, + EulerBasis::ZSX, +]; + fn matmul_1q(operator: &mut [[Complex64; 2]; 2], other: Array2) { *operator = [ [ @@ -1054,7 +1194,6 @@ pub fn euler_one_qubit_decomposer(m: &Bound) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(generate_circuit))?; m.add_wrapped(wrap_pyfunction!(unitary_to_gate_sequence))?; m.add_wrapped(wrap_pyfunction!(unitary_to_circuit))?; - m.add_wrapped(wrap_pyfunction!(compute_error_one_qubit_sequence))?; m.add_wrapped(wrap_pyfunction!(compute_error_list))?; m.add_wrapped(wrap_pyfunction!(optimize_1q_gates_decomposition))?; m.add_class::()?; diff --git a/crates/accelerate/src/nlayout.rs b/crates/accelerate/src/nlayout.rs index e0235e5c954a..e738b11e5338 100644 --- a/crates/accelerate/src/nlayout.rs +++ b/crates/accelerate/src/nlayout.rs @@ -14,6 +14,7 @@ use pyo3::prelude::*; use pyo3::types::PyList; use hashbrown::HashMap; +use qiskit_circuit::Qubit; /// A newtype for the different categories of qubits used within layouts. This is to enforce /// significantly more type safety when dealing with mixtures of physical and virtual qubits, as we @@ -24,7 +25,7 @@ use hashbrown::HashMap; macro_rules! qubit_newtype { ($id: ident) => { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub struct $id(u32); + pub struct $id(pub u32); impl $id { #[inline] @@ -72,6 +73,25 @@ impl PhysicalQubit { layout.phys_to_virt[self.index()] } } + +/// This is only safe in the context of a physical circuit during transpilation +/// after the qubit indices of the circuit/dag circuit refer to the physical +/// qubits (once we've run a layout pass and applied it). +impl From for PhysicalQubit { + fn from(s: Qubit) -> PhysicalQubit { + PhysicalQubit::new(s.0) + } +} + +/// This is only safe in the context of a physical circuit during transpilation +/// after the qubit indices of the circuit/dag circuit refer to the physical +/// qubits (once we've run a layout pass and applied it). +impl From<&Qubit> for PhysicalQubit { + fn from(s: &Qubit) -> PhysicalQubit { + PhysicalQubit::new(s.0) + } +} + qubit_newtype!(VirtualQubit); impl VirtualQubit { /// Get the physical qubit that currently corresponds to this index of virtual qubit in the diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 6d6e105b265d..08b3e90eabba 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -940,6 +940,17 @@ impl Target { }); } + /// Get the error rate of a given instruction in the target + pub fn get_error(&self, name: &str, qargs: &[PhysicalQubit]) -> Option { + self.gate_map.get(name).and_then(|gate_props| { + let qargs_key: Qargs = qargs.iter().cloned().collect(); + match gate_props.get(Some(&qargs_key)) { + Some(props) => props.as_ref().and_then(|inst_props| inst_props.error), + None => None, + } + }) + } + /// Get an iterator over the indices of all physical qubits of the target pub fn physical_qubits(&self) -> impl ExactSizeIterator { 0..self.num_qubits.unwrap_or_default() diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index bb5fff5e343a..59eb01728dca 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -22,8 +22,8 @@ use crate::dag_node::{DAGInNode, DAGNode, DAGOpNode, DAGOutNode}; use crate::dot_utils::build_dot; use crate::error::DAGCircuitError; use crate::imports; -use crate::interner::{IndexedInterner, Interner}; -use crate::operations::{Operation, OperationRef, Param, PyInstruction}; +use crate::interner::{Index, IndexedInterner, Interner}; +use crate::operations::{Operation, OperationRef, Param, PyInstruction, StandardGate}; use crate::packed_instruction::PackedInstruction; use crate::rustworkx_core_vnext::isomorphism; use crate::{BitType, Clbit, Qubit, TupleLikeArg}; @@ -3884,7 +3884,7 @@ def _format(operand): /// include_directives (bool): include `barrier`, `snapshot` etc. /// /// Returns: - /// list[DAGOpNode]: the list of node ids containing the given op. + /// list[DAGOpNode]: the list of dag nodes containing the given op. #[pyo3(name= "op_nodes", signature=(op=None, include_directives=true))] fn py_op_nodes( &self, @@ -3920,6 +3920,26 @@ def _format(operand): Ok(nodes) } + /// Get a list of "op" nodes in the dag that contain control flow instructions. + /// + /// Returns: + /// list[DAGOpNode]: The list of dag nodes containing control flow ops. + fn control_flow_op_nodes(&self, py: Python) -> PyResult>> { + self.dag + .node_references() + .filter_map(|(node_index, node_type)| match node_type { + NodeType::Operation(ref node) => { + if node.op.control_flow() { + Some(self.unpack_into(py, node_index, node_type)) + } else { + None + } + } + _ => None, + }) + .collect() + } + /// Get the list of gate nodes in the dag. /// /// Returns: @@ -4996,31 +5016,6 @@ def _format(operand): Ok(result) } - fn _insert_1q_on_incoming_qubit( - &mut self, - py: Python, - node: &Bound, - old_index: usize, - ) -> PyResult<()> { - if let NodeType::Operation(inst) = self.pack_into(py, node)? { - self.increment_op(inst.op.name()); - let new_index = self.dag.add_node(NodeType::Operation(inst)); - let old_index: NodeIndex = NodeIndex::new(old_index); - let (parent_index, edge_index, weight) = self - .dag - .edges_directed(old_index, Incoming) - .map(|edge| (edge.source(), edge.id(), edge.weight().clone())) - .next() - .unwrap(); - self.dag.add_edge(parent_index, new_index, weight.clone()); - self.dag.add_edge(new_index, old_index, weight); - self.dag.remove_edge(edge_index); - Ok(()) - } else { - Err(PyTypeError::new_err("Invalid node type input")) - } - } - fn _edges(&self, py: Python) -> Vec { self.dag .edge_indices() @@ -5615,7 +5610,7 @@ impl DAGCircuit { /// Remove an operation node n. /// /// Add edges from predecessors to successors. - fn remove_op_node(&mut self, index: NodeIndex) { + pub fn remove_op_node(&mut self, index: NodeIndex) { let mut edge_list: Vec<(NodeIndex, NodeIndex, Wire)> = Vec::new(); for (source, in_weight) in self .dag @@ -6167,6 +6162,122 @@ impl DAGCircuit { } Ok(()) } + /// Get qargs/qubits from an intern index + pub fn get_qubits(&self, index: Index) -> &[Qubit] { + self.qargs_cache.intern(index) + } + + /// Insert a new 1q standard gate on incoming qubit + pub fn insert_1q_on_incoming_qubit( + &mut self, + new_gate: (StandardGate, &[f64]), + old_index: NodeIndex, + ) { + 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: None, + #[cfg(feature = "cache_pygates")] + py_op: OnceCell::new(), + } + } else { + panic!("This method only works if provided index is an op node"); + }; + let new_index = self.dag.add_node(NodeType::Operation(inst)); + let (parent_index, edge_index, weight) = self + .dag + .edges_directed(old_index, Incoming) + .map(|edge| (edge.source(), edge.id(), edge.weight().clone())) + .next() + .unwrap(); + self.dag.add_edge(parent_index, new_index, weight.clone()); + self.dag.add_edge(new_index, old_index, weight); + self.dag.remove_edge(edge_index); + } + + /// Remove a sequence of 1 qubit nodes from the dag + /// This must only be called if all the nodes operate + /// on a single qubit with no other wires in or out of any nodes + pub fn remove_1q_sequence(&mut self, sequence: &[NodeIndex]) { + let (parent_index, weight) = self + .dag + .edges_directed(*sequence.first().unwrap(), Incoming) + .map(|edge| (edge.source(), edge.weight().clone())) + .next() + .unwrap(); + let child_index = self + .dag + .edges_directed(*sequence.last().unwrap(), Outgoing) + .map(|edge| edge.target()) + .next() + .unwrap(); + self.dag.add_edge(parent_index, child_index, weight); + for node in sequence { + match self.dag.remove_node(*node) { + Some(NodeType::Operation(packed)) => { + let op_name = packed.op.name(); + self.decrement_op(op_name); + } + _ => panic!("Must be called with valid operation node!"), + } + } + } + + pub fn add_global_phase(&mut self, py: Python, value: &Param) -> PyResult<()> { + match value { + Param::Obj(_) => { + return Err(PyTypeError::new_err( + "Invalid parameter type, only float and parameter expression are supported", + )) + } + _ => self.set_global_phase(add_global_phase(py, &self.global_phase, value)?)?, + } + Ok(()) + } + + pub fn calibrations_empty(&self) -> bool { + self.calibrations.is_empty() + } + + 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()) { + 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; + } + } + out_params.push(p.to_object(py)); + } + PyTuple::new_bound(py, out_params) + } + None => PyTuple::empty_bound(py), + }; + let qargs = self.qargs_cache.intern(instruction.qubits); + let qubits = PyTuple::new_bound(py, qargs.iter().map(|x| x.0)); + self.calibrations[instruction.op.name()] + .bind(py) + .contains((qubits, params).to_object(py)) + } else { + Err(DAGCircuitError::new_err("Specified node is not an op node")) + } + } } /// Add to global phase. Global phase can only be Float or ParameterExpression so this diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index 181f02e312b3..f7d768b69c50 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -35,7 +35,6 @@ from qiskit.circuit import Qubit from qiskit.circuit.quantumcircuitdata import CircuitInstruction from qiskit.dagcircuit.dagcircuit import DAGCircuit -from qiskit.dagcircuit.dagnode import DAGOpNode logger = logging.getLogger(__name__) @@ -84,7 +83,7 @@ def __init__(self, basis=None, target=None): self._basis_gates = basis self._target = target - self._global_decomposers = [] + self._global_decomposers = None self._local_decomposers_cache = {} if basis: @@ -209,46 +208,16 @@ def run(self, dag): Returns: DAGCircuit: the optimized DAG. """ - runs = [] - qubits = [] - bases = [] - for run in dag.collect_1q_runs(): - qubit = dag.find_bit(run[0].qargs[0]).index - runs.append(run) - qubits.append(qubit) - bases.append(self._get_decomposer(qubit)) - best_sequences = euler_one_qubit_decomposer.optimize_1q_gates_decomposition( - runs, qubits, bases, simplify=True, error_map=self.error_map + if self._basis_gates is None: + basis_gates = None + else: + basis_gates = set(self._basis_gates) + euler_one_qubit_decomposer.optimize_1q_gates_decomposition( + dag, + target=self._target, + global_decomposers=self._global_decomposers, + basis_gates=basis_gates, ) - for index, best_circuit_sequence in enumerate(best_sequences): - run = runs[index] - qubit = qubits[index] - if self._target is None: - basis = self._basis_gates - else: - basis = self._target.operation_names_for_qargs((qubit,)) - if best_circuit_sequence is not None: - (old_error, new_error, best_circuit_sequence) = best_circuit_sequence - if self._substitution_checks( - dag, - run, - best_circuit_sequence, - basis, - qubit, - old_error=old_error, - new_error=new_error, - ): - first_node_id = run[0]._node_id - qubit = run[0].qargs - for gate, angles in best_circuit_sequence: - op = CircuitInstruction.from_standard(gate, qubit, angles) - node = DAGOpNode.from_instruction(op) - dag._insert_1q_on_incoming_qubit(node, first_node_id) - dag.global_phase += best_circuit_sequence.global_phase - # Delete the other nodes in the run - for current_node in run: - dag.remove_op_node(current_node) - return dag def _error(self, circuit, qubit): diff --git a/qiskit/transpiler/passes/utils/control_flow.py b/qiskit/transpiler/passes/utils/control_flow.py index 27c3c83d53c7..9b471aadf928 100644 --- a/qiskit/transpiler/passes/utils/control_flow.py +++ b/qiskit/transpiler/passes/utils/control_flow.py @@ -54,7 +54,7 @@ def out(self, dag): def bound_wrapped_method(dag): return out(self, dag) - for node in dag.op_nodes(ControlFlowOp): + for node in dag.control_flow_op_nodes(): dag.substitute_node( node, map_blocks(bound_wrapped_method, node.op), propagate_condition=False ) diff --git a/releasenotes/notes/optimize-1q-gates-decomposition-ce111961b6782ee0.yaml b/releasenotes/notes/optimize-1q-gates-decomposition-ce111961b6782ee0.yaml new file mode 100644 index 000000000000..49b90f85e6ee --- /dev/null +++ b/releasenotes/notes/optimize-1q-gates-decomposition-ce111961b6782ee0.yaml @@ -0,0 +1,13 @@ +--- +features_transpiler: + - | + Added a new method :meth:`.DAGCircuit.control_flow_ops` which provides a fast + path to get all the :class:`.DAGOpNode` in a :class:`.DAGCircuit` that + contain a :class:`.ControlFlowOp`. This was possible before using the + :meth:`.DAGCircuit.op_nodes` method and passing the ``ControlFlowOp`` class + as a filter, but this new function will perform the same operation but + perform it faster. + - | + Ported the entirety of the :class:`.Optimize1qGatesDecomposition` transpiler + pass to Rust. This improves the runtime performance of the pass between 5x + to 10x.