From 0c8b668f1d16783ed864b6dcbf3366692fce82b3 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 | 315 +++++++++++++----- crates/accelerate/src/nlayout.rs | 22 +- .../accelerate/src/target_transpiler/mod.rs | 11 + crates/circuit/src/dag_circuit.rs | 169 ++++++++-- .../optimization/optimize_1q_decomposition.py | 51 +-- .../transpiler/passes/utils/control_flow.py | 2 +- ...-gates-decomposition-ce111961b6782ee0.yaml | 13 + 7 files changed, 423 insertions(+), 160 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..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 67849a6f2281..dac1d70b4658 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}; @@ -3880,7 +3880,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, @@ -3916,6 +3916,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: @@ -4992,31 +5012,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() @@ -5616,7 +5611,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 @@ -6168,6 +6163,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.