From 5c9d285f5c302c92c5b9762dfe804f0e2dbc26b2 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 13 Aug 2024 13:49:50 -0400 Subject: [PATCH] Fully port Optimize1qGatesDecomposition to Rust This commit builds off of #12550 and the other data model in Rust infrastructure and migrates the Optimize1qGatesDecomposition pass to operate fully in Rust. The full path of the transpiler pass now never leaves rust until it has finished modifying the DAGCircuit. There is still some python interaction necessary to handle parts of the data model that are still in Python, mainly calibrations and parameter expressions (for global phase). But otherwise the entirety of the pass operates in rust now. This is just a first pass at the migration here, it moves the pass to be a single for loop in rust. The next steps here are to look at operating the pass in parallel. There is no data dependency between the optimizations being done by the pass so we should be able to the throughput of the pass by leveraging multithreading to handle each run in parallel. This commit does not attempt this though, because of the Python dependency and also the data structures around gates and the dag aren't really setup for multithreading yet and there likely will need to be some work to support that (this pass is a good candidate to work through the bugs on that). Part of #12208 --- .../src/euler_one_qubit_decomposer.rs | 287 ++++++++++++++---- crates/accelerate/src/nlayout.rs | 22 +- .../accelerate/src/target_transpiler/mod.rs | 11 + crates/circuit/src/dag_circuit.rs | 154 +++++++--- .../optimization/optimize_1q_decomposition.py | 51 +--- .../transpiler/passes/utils/control_flow.py | 2 +- ...-gates-decomposition-ce111961b6782ee0.yaml | 13 + 7 files changed, 402 insertions(+), 138 deletions(-) create mode 100644 releasenotes/notes/optimize-1q-gates-decomposition-ce111961b6782ee0.yaml diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index 75a2f7993f86..0b009eb2e0ec 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -13,7 +13,8 @@ #![allow(clippy::too_many_arguments)] #![allow(clippy::upper_case_acronyms)] -use hashbrown::HashMap; +use hashbrown::{HashMap, HashSet}; +use indexmap::IndexSet; use num_complex::{Complex64, ComplexFloat}; use smallvec::{smallvec, SmallVec}; use std::cmp::Ordering; @@ -29,14 +30,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 +75,7 @@ impl OneQubitGateErrorMap { } } +#[derive(Debug)] #[pyclass(sequence)] pub struct OneQubitGateSequence { pub gates: Vec<(StandardGate, SmallVec<[f64; 3]>)>, @@ -571,7 +578,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, @@ -965,72 +972,232 @@ 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))?; + for node in raw_run { + dag.remove_op_node(node); + } + } + } + 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 = [ [ 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 6f590c7fc774..12674263f221 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}; @@ -70,7 +70,7 @@ static CONTROL_FLOW_OP_NAMES: [&str; 4] = ["for_loop", "while_loop", "if_else", static SEMANTIC_EQ_SYMMETRIC: [&str; 4] = ["barrier", "swap", "break_loop", "continue_loop"]; #[derive(Clone, Debug)] -pub(crate) enum NodeType { +pub enum NodeType { QubitIn(Qubit), QubitOut(Qubit), ClbitIn(Clbit), @@ -81,7 +81,7 @@ pub(crate) enum NodeType { } #[derive(Clone, Debug)] -pub(crate) enum Wire { +pub enum Wire { Qubit(Qubit), Clbit(Clbit), Var(PyObject), @@ -238,7 +238,7 @@ pub struct DAGCircuit { calibrations: HashMap>, - pub(crate) dag: StableDiGraph, + pub dag: StableDiGraph, #[pyo3(get)] qregs: Py, @@ -246,7 +246,7 @@ pub struct DAGCircuit { cregs: Py, /// The cache used to intern instruction qargs. - qargs_cache: IndexedInterner>, + pub qargs_cache: IndexedInterner>, /// The cache used to intern instruction cargs. cargs_cache: IndexedInterner>, /// Qubits registered in the circuit. @@ -2466,12 +2466,12 @@ def _format(operand): /// num_qubits() replaces former use of width(). /// DAGCircuit.width() now returns qubits + clbits for /// consistency with Circuit.width() [qiskit-terra #2564]. - fn num_qubits(&self) -> usize { + pub fn num_qubits(&self) -> usize { self.qubits.len() } /// Return the total number of classical bits used by the circuit. - fn num_clbits(&self) -> usize { + pub fn num_clbits(&self) -> usize { self.clbits.len() } @@ -4232,7 +4232,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, @@ -4257,6 +4257,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: @@ -5304,31 +5324,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().to_string()); - 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() @@ -5938,7 +5933,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 @@ -6445,6 +6440,95 @@ 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().to_string()); + 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: RefCell::new(None), + } + } 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); + } + + 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.