diff --git a/crates/accelerate/src/consolidate_blocks.rs b/crates/accelerate/src/consolidate_blocks.rs new file mode 100644 index 000000000000..1edd592ce877 --- /dev/null +++ b/crates/accelerate/src/consolidate_blocks.rs @@ -0,0 +1,319 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2024 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use hashbrown::{HashMap, HashSet}; +use ndarray::{aview2, Array2}; +use num_complex::Complex64; +use numpy::{IntoPyArray, PyReadonlyArray2}; +use pyo3::intern; +use pyo3::prelude::*; +use rustworkx_core::petgraph::stable_graph::NodeIndex; + +use qiskit_circuit::circuit_data::CircuitData; +use qiskit_circuit::dag_circuit::DAGCircuit; +use qiskit_circuit::gate_matrix::{ONE_QUBIT_IDENTITY, TWO_QUBIT_IDENTITY}; +use qiskit_circuit::imports::{QI_OPERATOR, QUANTUM_CIRCUIT, UNITARY_GATE}; +use qiskit_circuit::operations::{Operation, Param}; +use qiskit_circuit::Qubit; + +use crate::convert_2q_block_matrix::{blocks_to_matrix, get_matrix_from_inst}; +use crate::euler_one_qubit_decomposer::matmul_1q; +use crate::nlayout::PhysicalQubit; +use crate::target_transpiler::Target; +use crate::two_qubit_decompose::TwoQubitBasisDecomposer; + +fn is_supported( + target: Option<&Target>, + basis_gates: Option<&HashSet>, + name: &str, + qargs: &[Qubit], +) -> bool { + match target { + Some(target) => { + let physical_qargs = qargs.iter().map(|bit| PhysicalQubit(bit.0)).collect(); + target.instruction_supported(name, Some(&physical_qargs)) + } + None => match basis_gates { + Some(basis_gates) => basis_gates.contains(name), + None => true, + }, + } +} + +// If depth > 20, there will be 1q gates to consolidate. +const MAX_2Q_DEPTH: usize = 20; + +#[allow(clippy::too_many_arguments)] +#[pyfunction] +#[pyo3(signature = (dag, decomposer, force_consolidate, target=None, basis_gates=None, blocks=None, runs=None))] +pub(crate) fn consolidate_blocks( + py: Python, + dag: &mut DAGCircuit, + decomposer: &TwoQubitBasisDecomposer, + force_consolidate: bool, + target: Option<&Target>, + basis_gates: Option>, + blocks: Option>>, + runs: Option>>, +) -> PyResult<()> { + let blocks = match blocks { + Some(runs) => runs + .into_iter() + .map(|run| { + run.into_iter() + .map(NodeIndex::new) + .collect::>() + }) + .collect(), + // If runs are specified but blocks are none we're in a legacy configuration where external + // collection passes are being used. In this case don't collect blocks because it's + // unexpected. + None => match runs { + Some(_) => vec![], + None => dag.collect_2q_runs().unwrap(), + }, + }; + + let runs: Option>> = runs.map(|runs| { + runs.into_iter() + .map(|run| { + run.into_iter() + .map(NodeIndex::new) + .collect::>() + }) + .collect() + }); + let mut all_block_gates: HashSet = + HashSet::with_capacity(blocks.iter().map(|x| x.len()).sum()); + let mut block_qargs: HashSet = HashSet::with_capacity(2); + for block in blocks { + block_qargs.clear(); + if block.len() == 1 { + let inst_node = block[0]; + let inst = dag.dag()[inst_node].unwrap_operation(); + if !is_supported( + target, + basis_gates.as_ref(), + inst.op.name(), + dag.get_qargs(inst.qubits), + ) { + all_block_gates.insert(inst_node); + let matrix = match get_matrix_from_inst(py, inst) { + Ok(mat) => mat, + Err(_) => continue, + }; + let array = matrix.into_pyarray_bound(py); + let unitary_gate = UNITARY_GATE + .get_bound(py) + .call1((array, py.None(), false))?; + dag.substitute_node_with_py_op(py, inst_node, &unitary_gate, false)?; + continue; + } + } + let mut basis_count: usize = 0; + let mut outside_basis = false; + for node in &block { + let inst = dag.dag()[*node].unwrap_operation(); + block_qargs.extend(dag.get_qargs(inst.qubits)); + all_block_gates.insert(*node); + if inst.op.name() == decomposer.gate_name() { + basis_count += 1; + } + if !is_supported( + target, + basis_gates.as_ref(), + inst.op.name(), + dag.get_qargs(inst.qubits), + ) { + outside_basis = true; + } + } + if block_qargs.len() > 2 { + let mut qargs: Vec = block_qargs.iter().copied().collect(); + qargs.sort(); + let block_index_map: HashMap = qargs + .into_iter() + .enumerate() + .map(|(idx, qubit)| (qubit, idx)) + .collect(); + let circuit_data = CircuitData::from_packed_operations( + py, + block_qargs.len() as u32, + 0, + block.iter().map(|node| { + let inst = dag.dag()[*node].unwrap_operation(); + + Ok(( + inst.op.clone(), + inst.params_view().iter().cloned().collect(), + dag.get_qargs(inst.qubits) + .iter() + .map(|x| Qubit::new(block_index_map[x])) + .collect(), + vec![], + )) + }), + Param::Float(0.), + )?; + let circuit = QUANTUM_CIRCUIT + .get_bound(py) + .call_method1(intern!(py, "_from_circuit_data"), (circuit_data,))?; + let array = QI_OPERATOR + .get_bound(py) + .call1((circuit,))? + .getattr(intern!(py, "data"))? + .extract::>()?; + let matrix = array.as_array(); + let identity: Array2 = Array2::eye(2usize.pow(block_qargs.len() as u32)); + if approx::abs_diff_eq!(identity, matrix) { + for node in block { + dag.remove_op_node(node); + } + } else { + let unitary_gate = + UNITARY_GATE + .get_bound(py) + .call1((array.to_object(py), py.None(), false))?; + let clbit_pos_map = HashMap::new(); + dag.replace_block_with_py_op( + py, + &block, + unitary_gate, + false, + &block_index_map, + &clbit_pos_map, + )?; + } + } else { + let block_index_map = [ + *block_qargs.iter().min().unwrap(), + *block_qargs.iter().max().unwrap(), + ]; + let matrix = blocks_to_matrix(py, dag, &block, block_index_map).ok(); + if let Some(matrix) = matrix { + if force_consolidate + || decomposer.num_basis_gates_inner(matrix.view()) < basis_count + || block.len() > MAX_2Q_DEPTH + || (basis_gates.is_some() && outside_basis) + || (target.is_some() && outside_basis) + { + if approx::abs_diff_eq!(aview2(&TWO_QUBIT_IDENTITY), matrix) { + for node in block { + dag.remove_op_node(node); + } + } else { + let array = matrix.into_pyarray_bound(py); + let unitary_gate = + UNITARY_GATE + .get_bound(py) + .call1((array, py.None(), false))?; + let qubit_pos_map = block_index_map + .into_iter() + .enumerate() + .map(|(idx, qubit)| (qubit, idx)) + .collect(); + let clbit_pos_map = HashMap::new(); + dag.replace_block_with_py_op( + py, + &block, + unitary_gate, + false, + &qubit_pos_map, + &clbit_pos_map, + )?; + } + } + } + } + } + if let Some(runs) = runs { + for run in runs { + if run.iter().any(|node| all_block_gates.contains(node)) { + continue; + } + 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); + + if run.len() == 1 + && !is_supported( + target, + basis_gates.as_ref(), + first_inst.op.name(), + first_qubits, + ) + { + let matrix = match get_matrix_from_inst(py, first_inst) { + Ok(mat) => mat, + Err(_) => continue, + }; + let array = matrix.into_pyarray_bound(py); + let unitary_gate = UNITARY_GATE + .get_bound(py) + .call1((array, py.None(), false))?; + dag.substitute_node_with_py_op(py, first_inst_node, &unitary_gate, false)?; + continue; + } + let qubit = first_qubits[0]; + let mut matrix = ONE_QUBIT_IDENTITY; + + let mut already_in_block = false; + for node in &run { + if all_block_gates.contains(node) { + already_in_block = true; + } + let gate = dag.dag()[*node].unwrap_operation(); + let operator = match get_matrix_from_inst(py, gate) { + Ok(mat) => mat, + Err(_) => { + // Set this to skip this run because we can't compute the matrix of the + // operation. + already_in_block = true; + break; + } + }; + matmul_1q(&mut matrix, operator); + } + if already_in_block { + continue; + } + if approx::abs_diff_eq!(aview2(&ONE_QUBIT_IDENTITY), aview2(&matrix)) { + for node in run { + dag.remove_op_node(node); + } + } else { + let array = aview2(&matrix).to_owned().into_pyarray_bound(py); + let unitary_gate = UNITARY_GATE + .get_bound(py) + .call1((array, py.None(), false))?; + let mut block_index_map: HashMap = HashMap::with_capacity(1); + block_index_map.insert(qubit, 0); + let clbit_pos_map = HashMap::new(); + dag.replace_block_with_py_op( + py, + &run, + unitary_gate, + false, + &block_index_map, + &clbit_pos_map, + )?; + } + } + } + + Ok(()) +} + +pub fn consolidate_blocks_mod(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(consolidate_blocks))?; + Ok(()) +} diff --git a/crates/accelerate/src/convert_2q_block_matrix.rs b/crates/accelerate/src/convert_2q_block_matrix.rs index dc4d0b77c4a7..aefc5976e82f 100644 --- a/crates/accelerate/src/convert_2q_block_matrix.rs +++ b/crates/accelerate/src/convert_2q_block_matrix.rs @@ -12,102 +12,135 @@ use pyo3::intern; use pyo3::prelude::*; -use pyo3::types::PyDict; -use pyo3::wrap_pyfunction; use pyo3::Python; use num_complex::Complex64; use numpy::ndarray::linalg::kron; use numpy::ndarray::{aview2, Array2, ArrayView2}; -use numpy::{IntoPyArray, PyArray2, PyReadonlyArray2}; -use smallvec::SmallVec; +use numpy::PyReadonlyArray2; +use rustworkx_core::petgraph::stable_graph::NodeIndex; -use qiskit_circuit::bit_data::BitData; -use qiskit_circuit::circuit_instruction::CircuitInstruction; -use qiskit_circuit::dag_node::DAGOpNode; +use qiskit_circuit::dag_circuit::DAGCircuit; use qiskit_circuit::gate_matrix::ONE_QUBIT_IDENTITY; use qiskit_circuit::imports::QI_OPERATOR; -use qiskit_circuit::operations::Operation; +use qiskit_circuit::operations::{Operation, OperationRef}; +use qiskit_circuit::packed_instruction::PackedInstruction; +use qiskit_circuit::Qubit; +use crate::euler_one_qubit_decomposer::matmul_1q; use crate::QiskitError; -fn get_matrix_from_inst<'py>( - py: Python<'py>, - inst: &'py CircuitInstruction, -) -> PyResult> { - if let Some(mat) = inst.operation.matrix(&inst.params) { +#[inline] +pub fn get_matrix_from_inst(py: Python, inst: &PackedInstruction) -> PyResult> { + if let Some(mat) = inst.op.matrix(inst.params_view()) { Ok(mat) - } else if inst.operation.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 { + } else if let OperationRef::Gate(gate) = inst.op.view() { Ok(QI_OPERATOR .get_bound(py) - .call1((inst.get_operation(py)?,))? + .call1((gate.gate.clone_ref(py),))? .getattr(intern!(py, "data"))? .extract::>()? .as_array() .to_owned()) + } else { + Err(QiskitError::new_err( + "Can't compute matrix of non-unitary op", + )) } } /// Return the matrix Operator resulting from a block of Instructions. -#[pyfunction] -#[pyo3(text_signature = "(op_list, /")] pub fn blocks_to_matrix( py: Python, - op_list: Vec>, - block_index_map_dict: &Bound, -) -> PyResult>> { - // Build a BitData in block_index_map_dict order. block_index_map_dict is a dict of bits to - // indices mapping the order of the qargs in the block. There should only be 2 entries since - // there are only 2 qargs here (e.g. `{Qubit(): 0, Qubit(): 1}`) so we need to ensure that - // we added the qubits to bit data in the correct index order. - let mut index_map: Vec = (0..block_index_map_dict.len()).map(|_| py.None()).collect(); - for bit_tuple in block_index_map_dict.items() { - let (bit, index): (PyObject, usize) = bit_tuple.extract()?; - index_map[index] = bit; - } - let mut bit_map: BitData = BitData::new(py, "qargs".to_string()); - for bit in index_map { - bit_map.add(py, bit.bind(py), true)?; - } - let identity = aview2(&ONE_QUBIT_IDENTITY); - let first_node = &op_list[0]; - let input_matrix = get_matrix_from_inst(py, &first_node.instruction)?; - let mut matrix: Array2 = match bit_map - .map_bits(first_node.instruction.qubits.bind(py).iter())? - .collect::>() - .as_slice() - { - [0] => kron(&identity, &input_matrix), - [1] => kron(&input_matrix, &identity), - [0, 1] => input_matrix, - [1, 0] => change_basis(input_matrix.view()), - [] => Array2::eye(4), - _ => unreachable!(), + dag: &DAGCircuit, + op_list: &[NodeIndex], + block_index_map: [Qubit; 2], +) -> PyResult> { + let map_bits = |bit: &Qubit| -> u8 { + if *bit == block_index_map[0] { + 0 + } else { + 1 + } }; - for node in op_list.into_iter().skip(1) { - let op_matrix = get_matrix_from_inst(py, &node.instruction)?; - let q_list = bit_map - .map_bits(node.instruction.qubits.bind(py).iter())? - .map(|x| x as u8) - .collect::>(); - - let result = match q_list.as_slice() { - [0] => Some(kron(&identity, &op_matrix)), - [1] => Some(kron(&op_matrix, &identity)), - [1, 0] => Some(change_basis(op_matrix.view())), - [] => Some(Array2::eye(4)), - _ => None, - }; - matrix = match result { - Some(result) => result.dot(&matrix), - None => op_matrix.dot(&matrix), - }; + let mut qubit_0 = ONE_QUBIT_IDENTITY; + let mut qubit_1 = ONE_QUBIT_IDENTITY; + let mut one_qubit_components_modified = false; + let mut output_matrix: Option> = None; + for node in op_list { + let inst = dag.dag()[*node].unwrap_operation(); + let op_matrix = get_matrix_from_inst(py, inst)?; + match dag + .get_qargs(inst.qubits) + .iter() + .map(map_bits) + .collect::>() + .as_slice() + { + [0] => { + matmul_1q(&mut qubit_0, op_matrix); + one_qubit_components_modified = true; + } + [1] => { + matmul_1q(&mut qubit_1, op_matrix); + one_qubit_components_modified = true; + } + [0, 1] => { + if one_qubit_components_modified { + let one_qubits_combined = kron(&aview2(&qubit_1), &aview2(&qubit_0)); + output_matrix = Some(match output_matrix { + None => op_matrix.dot(&one_qubits_combined), + Some(current) => { + let temp = one_qubits_combined.dot(¤t); + op_matrix.dot(&temp) + } + }); + qubit_0 = ONE_QUBIT_IDENTITY; + qubit_1 = ONE_QUBIT_IDENTITY; + one_qubit_components_modified = false; + } else { + output_matrix = Some(match output_matrix { + None => op_matrix, + Some(current) => op_matrix.dot(¤t), + }); + } + } + [1, 0] => { + let matrix = change_basis(op_matrix.view()); + if one_qubit_components_modified { + let one_qubits_combined = kron(&aview2(&qubit_1), &aview2(&qubit_0)); + output_matrix = Some(match output_matrix { + None => matrix.dot(&one_qubits_combined), + Some(current) => matrix.dot(&one_qubits_combined.dot(¤t)), + }); + qubit_0 = ONE_QUBIT_IDENTITY; + qubit_1 = ONE_QUBIT_IDENTITY; + one_qubit_components_modified = false; + } else { + output_matrix = Some(match output_matrix { + None => matrix, + Some(current) => matrix.dot(¤t), + }); + } + } + _ => unreachable!(), + } } - Ok(matrix.into_pyarray_bound(py).unbind()) + Ok(match output_matrix { + Some(matrix) => { + if one_qubit_components_modified { + let one_qubits_combined = kron(&aview2(&qubit_1), &aview2(&qubit_0)); + one_qubits_combined.dot(&matrix) + } else { + matrix + } + } + None => kron(&aview2(&qubit_1), &aview2(&qubit_0)), + }) } /// Switches the order of qubits in a two qubit operation. @@ -123,8 +156,3 @@ pub fn change_basis(matrix: ArrayView2) -> Array2 { } trans_matrix } - -pub fn convert_2q_block_matrix(m: &Bound) -> PyResult<()> { - m.add_wrapped(wrap_pyfunction!(blocks_to_matrix))?; - Ok(()) -} diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index b5ed6014faaa..eb53b8309b05 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -1242,7 +1242,8 @@ pub(crate) fn optimize_1q_gates_decomposition( Ok(()) } -fn matmul_1q(operator: &mut [[Complex64; 2]; 2], other: Array2) { +#[inline(always)] +pub fn matmul_1q(operator: &mut [[Complex64; 2]; 2], other: Array2) { *operator = [ [ other[[0, 0]] * operator[0][0] + other[[0, 1]] * operator[1][0], diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index a0f9a6d72731..a3b6e6fa6e69 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -25,6 +25,7 @@ pub mod circuit_library; pub mod commutation_analysis; pub mod commutation_cancellation; pub mod commutation_checker; +pub mod consolidate_blocks; pub mod convert_2q_block_matrix; pub mod dense_layout; pub mod edge_collections; diff --git a/crates/accelerate/src/two_qubit_decompose.rs b/crates/accelerate/src/two_qubit_decompose.rs index fb8c58baab9d..48ba1fd0ad59 100644 --- a/crates/accelerate/src/two_qubit_decompose.rs +++ b/crates/accelerate/src/two_qubit_decompose.rs @@ -1337,6 +1337,17 @@ pub struct TwoQubitBasisDecomposer { q2r: Array2, } impl TwoQubitBasisDecomposer { + /// Return the KAK gate name + pub fn gate_name(&self) -> &str { + self.gate.as_str() + } + + /// Compute the number of basis gates needed for a given unitary + pub fn num_basis_gates_inner(&self, unitary: ArrayView2) -> usize { + let u = unitary.into_faer_complex(); + __num_basis_gates(self.basis_decomposer.b, self.basis_fidelity, u) + } + fn decomp1_inner( &self, target: &TwoQubitWeylDecomposition, diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 135ac289fff1..10551963b6e1 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -2808,117 +2808,14 @@ def _format(operand): } let block_ids: Vec<_> = node_block.iter().map(|n| n.node.unwrap()).collect(); - - let mut block_op_names = Vec::new(); - let mut block_qargs: HashSet = HashSet::new(); - let mut block_cargs: HashSet = HashSet::new(); - for nd in &block_ids { - 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)); - - if let Some(condition) = packed.condition() { - block_cargs.extend( - self.clbits.map_bits( - self.control_flow_module - .condition_resources(condition.bind(py))? - .clbits - .bind(py), - )?, - ); - continue; - } - - // Add classical bits from SwitchCaseOp, if applicable. - 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"))?; - if target.is_instance(imports::CLBIT.get_bound(py))? { - block_cargs.insert(self.clbits.find(&target).unwrap()); - } else if target - .is_instance(imports::CLASSICAL_REGISTER.get_bound(py))? - { - block_cargs.extend( - self.clbits - .map_bits(target.extract::>>()?)?, - ); - } else { - block_cargs.extend( - self.clbits.map_bits( - self.control_flow_module - .node_resources(&target)? - .clbits - .bind(py), - )?, - ); - } - } - } - } - Some(_) => { - return Err(DAGCircuitError::new_err( - "Nodes in 'node_block' must be of type 'DAGOpNode'.", - )) - } - None => { - return Err(DAGCircuitError::new_err( - "Node in 'node_block' not found in DAG.", - )) - } - } - } - - let mut block_qargs: Vec = block_qargs - .into_iter() - .filter(|q| qubit_pos_map.contains_key(q)) - .collect(); - block_qargs.sort_by_key(|q| qubit_pos_map[q]); - - let mut block_cargs: Vec = block_cargs - .into_iter() - .filter(|c| clbit_pos_map.contains_key(c)) - .collect(); - block_cargs.sort_by_key(|c| clbit_pos_map[c]); - - let py_op = op.extract::()?; - - if py_op.operation.num_qubits() as usize != block_qargs.len() { - return Err(DAGCircuitError::new_err(format!( - "Number of qubits in the replacement operation ({}) is not equal to the number of qubits in the block ({})!", py_op.operation.num_qubits(), block_qargs.len() - ))); - } - - 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, - 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(), - }); - - let new_node = self - .dag - .contract_nodes(block_ids, weight, cycle_check) - .map_err(|e| match e { - ContractError::DAGWouldCycle => DAGCircuitError::new_err( - "Replacing the specified node block would introduce a cycle", - ), - })?; - - self.increment_op(op_name.as_str()); - for name in block_op_names { - self.decrement_op(name.as_str()); - } - + let new_node = self.replace_block_with_py_op( + py, + &block_ids, + op, + cycle_check, + &qubit_pos_map, + &clbit_pos_map, + )?; self.get_node(py, new_node) } @@ -3447,140 +3344,17 @@ def _format(operand): }; let py = op.py(); let node_index = node.as_ref().node.unwrap(); - // Extract information from node that is going to be replaced - let old_packed = match self.dag.node_weight(node_index) { - Some(NodeType::Operation(old_packed)) => old_packed.clone(), - Some(_) => { - return Err(DAGCircuitError::new_err( - "'node' must be of type 'DAGOpNode'.", - )) - } - None => return Err(DAGCircuitError::new_err("'node' not found in DAG.")), - }; - // Extract information from new op - let new_op = op.extract::()?; - let current_wires: HashSet = self - .dag - .edges(node_index) - .map(|e| e.weight().clone()) - .collect(); - let mut new_wires: HashSet = self - .qargs_interner - .get(old_packed.qubits) - .iter() - .map(|x| Wire::Qubit(*x)) - .chain( - self.cargs_interner - .get(old_packed.clbits) - .iter() - .map(|x| Wire::Clbit(*x)), - ) - .collect(); - let (additional_clbits, additional_vars) = - self.additional_wires(py, new_op.operation.view(), new_op.extra_attrs.condition())?; - new_wires.extend(additional_clbits.iter().map(|x| Wire::Clbit(*x))); - new_wires.extend( - additional_vars - .iter() - .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() - { - 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() - ))); - } - - #[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 - && !(node.instruction.operation.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() { - return Err(DAGCircuitError::new_err( - "Cannot propagate a condition to an operation that already has one.", - )); - } - if let Some(old_condition) = old_packed.condition() { - if matches!(new_op.operation.view(), OperationRef::Operation(_)) { - return Err(DAGCircuitError::new_err( - "Cannot add a condition on a generic Operation.", - )); - } - extra_attrs.set_condition(Some(old_condition.clone_ref(py))); - - let binding = self - .control_flow_module - .condition_resources(old_condition.bind(py))?; - let condition_clbits = binding.clbits.bind(py); - for bit in condition_clbits { - new_wires.insert(Wire::Clbit(self.clbits.find(&bit).unwrap())); - } - let op_ref = new_op.operation.view(); - if let OperationRef::Instruction(inst) = op_ref { - inst.instruction - .bind(py) - .setattr(intern!(py, "condition"), old_condition)?; - } else if let OperationRef::Gate(gate) = op_ref { - gate.gate.bind(py).call_method1( - intern!(py, "c_if"), - old_condition.downcast_bound::(py)?, - )?; - } - #[cfg(feature = "cache_pygates")] - { - py_op_cache = None; - } - } - }; - if new_wires != current_wires { - // 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 - ))); - } - + self.substitute_node_with_py_op(py, node_index, op, propagate_condition)?; if inplace { - node.instruction.operation = new_op.operation.clone(); - node.instruction.params = new_op.params.clone(); - node.instruction.extra_attrs = extra_attrs.clone(); + 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(); #[cfg(feature = "cache_pygates")] { - node.instruction.py_op = py_op_cache - .as_ref() - .map(|ob| OnceCell::from(ob.clone_ref(py))) - .unwrap_or_default(); + node.instruction.py_op = new_weight.py_op.clone(); } - } - // Clone op data, as it will be moved into the PackedInstruction - let new_weight = NodeType::Operation(PackedInstruction { - op: new_op.operation.clone(), - qubits: old_packed.qubits, - clbits: old_packed.clbits, - params: (!new_op.params.is_empty()).then(|| new_op.params.into()), - extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op: py_op_cache.map(OnceCell::from).unwrap_or_default(), - }); - let node_index = node.as_ref().node.unwrap(); - if let Some(weight) = self.dag.node_weight_mut(node_index) { - *weight = new_weight; - } - - // Update self.op_names - self.decrement_op(old_packed.op.name()); - self.increment_op(new_op.operation.name()); - - if inplace { Ok(node.into_py(py)) } else { self.get_node(py, node_index) @@ -6988,6 +6762,249 @@ impl DAGCircuit { }; Self::from_circuit(py, circ, copy_op, None, None) } + + /// Replace a block of node indices with a new python operation + pub fn replace_block_with_py_op( + &mut self, + py: Python, + block_ids: &[NodeIndex], + op: Bound, + cycle_check: bool, + qubit_pos_map: &HashMap, + clbit_pos_map: &HashMap, + ) -> PyResult { + let mut block_op_names = Vec::with_capacity(block_ids.len()); + let mut block_qargs: HashSet = HashSet::new(); + let mut block_cargs: HashSet = HashSet::new(); + for nd in block_ids { + 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)); + + if let Some(condition) = packed.condition() { + block_cargs.extend( + self.clbits.map_bits( + self.control_flow_module + .condition_resources(condition.bind(py))? + .clbits + .bind(py), + )?, + ); + continue; + } + + // Add classical bits from SwitchCaseOp, if applicable. + 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"))?; + if target.is_instance(imports::CLBIT.get_bound(py))? { + block_cargs.insert(self.clbits.find(&target).unwrap()); + } else if target + .is_instance(imports::CLASSICAL_REGISTER.get_bound(py))? + { + block_cargs.extend( + self.clbits + .map_bits(target.extract::>>()?)?, + ); + } else { + block_cargs.extend( + self.clbits.map_bits( + self.control_flow_module + .node_resources(&target)? + .clbits + .bind(py), + )?, + ); + } + } + } + } + Some(_) => { + return Err(DAGCircuitError::new_err( + "Nodes in 'node_block' must be of type 'DAGOpNode'.", + )) + } + None => { + return Err(DAGCircuitError::new_err( + "Node in 'node_block' not found in DAG.", + )) + } + } + } + + let mut block_qargs: Vec = block_qargs + .into_iter() + .filter(|q| qubit_pos_map.contains_key(q)) + .collect(); + block_qargs.sort_by_key(|q| qubit_pos_map[q]); + + let mut block_cargs: Vec = block_cargs + .into_iter() + .filter(|c| clbit_pos_map.contains_key(c)) + .collect(); + block_cargs.sort_by_key(|c| clbit_pos_map[c]); + + let py_op = op.extract::()?; + + if py_op.operation.num_qubits() as usize != block_qargs.len() { + return Err(DAGCircuitError::new_err(format!( + "Number of qubits in the replacement operation ({}) is not equal to the number of qubits in the block ({})!", py_op.operation.num_qubits(), block_qargs.len() + ))); + } + + 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, + 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(), + }); + + let new_node = self + .dag + .contract_nodes(block_ids.iter().copied(), weight, cycle_check) + .map_err(|e| match e { + ContractError::DAGWouldCycle => DAGCircuitError::new_err( + "Replacing the specified node block would introduce a cycle", + ), + })?; + + self.increment_op(op_name.as_str()); + for name in block_op_names { + self.decrement_op(name.as_str()); + } + Ok(new_node) + } + + /// Substitute a give node in the dag with a new operation from python + pub fn substitute_node_with_py_op( + &mut self, + py: Python, + node_index: NodeIndex, + op: &Bound, + propagate_condition: bool, + ) -> 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(); + // Extract information from new op + let new_op = op.extract::()?; + let current_wires: HashSet = self + .dag + .edges(node_index) + .map(|e| e.weight().clone()) + .collect(); + let mut new_wires: HashSet = self + .qargs_interner + .get(old_packed.qubits) + .iter() + .map(|x| Wire::Qubit(*x)) + .chain( + self.cargs_interner + .get(old_packed.clbits) + .iter() + .map(|x| Wire::Clbit(*x)), + ) + .collect(); + let (additional_clbits, additional_vars) = + self.additional_wires(py, new_op.operation.view(), new_op.extra_attrs.condition())?; + new_wires.extend(additional_clbits.iter().map(|x| Wire::Clbit(*x))); + new_wires.extend( + additional_vars + .iter() + .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() + { + 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() + ))); + } + + #[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 && !(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() { + return Err(DAGCircuitError::new_err( + "Cannot propagate a condition to an operation that already has one.", + )); + } + if let Some(old_condition) = old_packed.condition() { + if matches!(new_op.operation.view(), OperationRef::Operation(_)) { + return Err(DAGCircuitError::new_err( + "Cannot add a condition on a generic Operation.", + )); + } + extra_attrs.set_condition(Some(old_condition.clone_ref(py))); + + let binding = self + .control_flow_module + .condition_resources(old_condition.bind(py))?; + let condition_clbits = binding.clbits.bind(py); + for bit in condition_clbits { + new_wires.insert(Wire::Clbit(self.clbits.find(&bit).unwrap())); + } + let op_ref = new_op.operation.view(); + if let OperationRef::Instruction(inst) = op_ref { + inst.instruction + .bind(py) + .setattr(intern!(py, "condition"), old_condition)?; + } else if let OperationRef::Gate(gate) = op_ref { + gate.gate.bind(py).call_method1( + intern!(py, "c_if"), + old_condition.downcast_bound::(py)?, + )?; + } + #[cfg(feature = "cache_pygates")] + { + py_op_cache = None; + } + } + }; + if new_wires != current_wires { + // 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 + ))); + } + 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()), + extra_attrs, + #[cfg(feature = "cache_pygates")] + py_op: py_op_cache.map(OnceCell::from).unwrap_or_default(), + }); + if let Some(weight) = self.dag.node_weight_mut(node_index) { + *weight = new_weight; + } + + // Update self.op_names + self.decrement_op(op_name.as_str()); + self.increment_op(new_op_name.as_str()); + Ok(()) + } } /// Add to global phase. Global phase can only be Float or ParameterExpression so this diff --git a/crates/circuit/src/gate_matrix.rs b/crates/circuit/src/gate_matrix.rs index 6b04b8512fb0..c6eabf1064fe 100644 --- a/crates/circuit/src/gate_matrix.rs +++ b/crates/circuit/src/gate_matrix.rs @@ -19,6 +19,12 @@ use crate::util::{ }; pub static ONE_QUBIT_IDENTITY: GateArray1Q = [[C_ONE, C_ZERO], [C_ZERO, C_ONE]]; +pub static TWO_QUBIT_IDENTITY: GateArray2Q = [ + [C_ONE, C_ZERO, C_ZERO, C_ZERO], + [C_ZERO, C_ONE, C_ZERO, C_ZERO], + [C_ZERO, C_ZERO, C_ONE, C_ZERO], + [C_ZERO, C_ZERO, C_ZERO, C_ONE], +]; // Utility for generating static matrices for controlled gates with "n" control qubits. // Assumptions: diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index 49070e85db70..2802098d8de9 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -35,7 +35,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, ::qiskit_accelerate::commutation_analysis::commutation_analysis, "commutation_analysis")?; add_submodule(m, ::qiskit_accelerate::commutation_cancellation::commutation_cancellation, "commutation_cancellation")?; add_submodule(m, ::qiskit_accelerate::commutation_checker::commutation_checker, "commutation_checker")?; - add_submodule(m, ::qiskit_accelerate::convert_2q_block_matrix::convert_2q_block_matrix, "convert_2q_block_matrix")?; + add_submodule(m, ::qiskit_accelerate::consolidate_blocks::consolidate_blocks_mod, "consolidate_blocks")?; add_submodule(m, ::qiskit_accelerate::dense_layout::dense_layout, "dense_layout")?; add_submodule(m, ::qiskit_accelerate::equivalence::equivalence, "equivalence")?; add_submodule(m, ::qiskit_accelerate::error_map::error_map, "error_map")?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index 202ebc32be85..b26b5d49f52a 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -58,7 +58,6 @@ sys.modules["qiskit._accelerate.converters"] = _accelerate.converters sys.modules["qiskit._accelerate.basis"] = _accelerate.basis sys.modules["qiskit._accelerate.basis.basis_translator"] = _accelerate.basis.basis_translator -sys.modules["qiskit._accelerate.convert_2q_block_matrix"] = _accelerate.convert_2q_block_matrix sys.modules["qiskit._accelerate.dense_layout"] = _accelerate.dense_layout sys.modules["qiskit._accelerate.equivalence"] = _accelerate.equivalence sys.modules["qiskit._accelerate.error_map"] = _accelerate.error_map @@ -97,6 +96,7 @@ sys.modules["qiskit._accelerate.commutation_checker"] = _accelerate.commutation_checker sys.modules["qiskit._accelerate.commutation_analysis"] = _accelerate.commutation_analysis sys.modules["qiskit._accelerate.commutation_cancellation"] = _accelerate.commutation_cancellation +sys.modules["qiskit._accelerate.consolidate_blocks"] = _accelerate.consolidate_blocks sys.modules["qiskit._accelerate.synthesis.linear_phase"] = _accelerate.synthesis.linear_phase sys.modules["qiskit._accelerate.synthesis.multi_controlled"] = ( _accelerate.synthesis.multi_controlled diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index 49f227e8a746..63dca11f6d2d 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -13,26 +13,22 @@ """Replace each block of consecutive gates by a single Unitary node.""" from __future__ import annotations -import numpy as np - -from qiskit.circuit.classicalregister import ClassicalRegister -from qiskit.circuit.quantumregister import QuantumRegister -from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.dagcircuit.dagnode import DAGOpNode -from qiskit.quantum_info import Operator from qiskit.synthesis.two_qubit import TwoQubitBasisDecomposer -from qiskit.circuit.library.generalized_gates.unitary import UnitaryGate -from qiskit.circuit.library.standard_gates import CXGate +from qiskit.circuit.library.standard_gates import CXGate, CZGate, iSwapGate, ECRGate from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.passmanager import PassManager -from qiskit.transpiler.passes.synthesis import unitary_synthesis -from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES -from qiskit._accelerate.convert_2q_block_matrix import blocks_to_matrix -from qiskit.exceptions import QiskitError +from qiskit._accelerate.consolidate_blocks import consolidate_blocks from .collect_1q_runs import Collect1qRuns from .collect_2q_blocks import Collect2qBlocks +KAK_GATE_NAMES = { + "cx": CXGate(), + "cz": CZGate(), + "iswap": iSwapGate(), + "ecr": ECRGate(), +} + class ConsolidateBlocks(TransformationPass): """Replace each block of consecutive gates by a single Unitary node. @@ -78,9 +74,13 @@ def __init__( if kak_basis_gate is not None: self.decomposer = TwoQubitBasisDecomposer(kak_basis_gate) elif basis_gates is not None: - self.decomposer = unitary_synthesis._decomposer_2q_from_basis_gates( - basis_gates, approximation_degree=approximation_degree - ) + kak_gates = KAK_GATE_NAMES.keys() & (basis_gates or []) + if kak_gates: + self.decomposer = TwoQubitBasisDecomposer( + KAK_GATE_NAMES[kak_gates.pop()], basis_fidelity=approximation_degree or 1.0 + ) + else: + self.decomposer = TwoQubitBasisDecomposer(CXGate()) else: self.decomposer = TwoQubitBasisDecomposer(CXGate()) @@ -93,89 +93,22 @@ def run(self, dag): if self.decomposer is None: return dag - blocks = self.property_set["block_list"] or [] - basis_gate_name = self.decomposer.gate.name - all_block_gates = set() - for block in blocks: - if len(block) == 1 and self._check_not_in_basis(dag, block[0].name, block[0].qargs): - all_block_gates.add(block[0]) - dag.substitute_node(block[0], UnitaryGate(block[0].op.to_matrix())) - else: - basis_count = 0 - outside_basis = False - block_qargs = set() - block_cargs = set() - for nd in block: - block_qargs |= set(nd.qargs) - if isinstance(nd, DAGOpNode) and getattr(nd, "condition", None): - block_cargs |= set(getattr(nd, "condition", None)[0]) - all_block_gates.add(nd) - block_index_map = self._block_qargs_to_indices(dag, block_qargs) - for nd in block: - if nd.name == basis_gate_name: - basis_count += 1 - if self._check_not_in_basis(dag, nd.name, nd.qargs): - outside_basis = True - if len(block_qargs) > 2: - q = QuantumRegister(len(block_qargs)) - qc = QuantumCircuit(q) - if block_cargs: - c = ClassicalRegister(len(block_cargs)) - qc.add_register(c) - for nd in block: - qc.append(nd.op, [q[block_index_map[i]] for i in nd.qargs]) - unitary = UnitaryGate(Operator(qc), check_input=False) - else: - try: - matrix = blocks_to_matrix(block, block_index_map) - except QiskitError: - # If building a matrix for the block fails we should not consolidate it - # because there is nothing we can do with it. - continue - unitary = UnitaryGate(matrix, check_input=False) - - max_2q_depth = 20 # If depth > 20, there will be 1q gates to consolidate. - if ( # pylint: disable=too-many-boolean-expressions - self.force_consolidate - or unitary.num_qubits > 2 - or self.decomposer.num_basis_gates(matrix) < basis_count - or len(block) > max_2q_depth - or ((self.basis_gates is not None) and outside_basis) - or ((self.target is not None) and outside_basis) - ): - identity = np.eye(2**unitary.num_qubits) - if np.allclose(identity, unitary.to_matrix()): - for node in block: - dag.remove_op_node(node) - else: - dag.replace_block_with_op( - block, unitary, block_index_map, cycle_check=False - ) - # If 1q runs are collected before consolidate those too - runs = self.property_set["run_list"] or [] - identity_1q = np.eye(2) - for run in runs: - if any(gate in all_block_gates for gate in run): - continue - if len(run) == 1 and not self._check_not_in_basis(dag, run[0].name, run[0].qargs): - dag.substitute_node(run[0], UnitaryGate(run[0].op.to_matrix(), check_input=False)) - else: - qubit = run[0].qargs[0] - operator = run[0].op.to_matrix() - already_in_block = False - for gate in run[1:]: - if gate in all_block_gates: - already_in_block = True - operator = gate.op.to_matrix().dot(operator) - if already_in_block: - continue - unitary = UnitaryGate(operator, check_input=False) - if np.allclose(identity_1q, unitary.to_matrix()): - for node in run: - dag.remove_op_node(node) - else: - dag.replace_block_with_op(run, unitary, {qubit: 0}, cycle_check=False) - + blocks = self.property_set["block_list"] + if blocks is not None: + blocks = [[node._node_id for node in block] for block in blocks] + runs = self.property_set["run_list"] + if runs is not None: + runs = [[node._node_id for node in run] for run in runs] + + consolidate_blocks( + dag, + self.decomposer._inner_decomposer, + self.force_consolidate, + target=self.target, + basis_gates=self.basis_gates, + blocks=blocks, + runs=runs, + ) dag = self._handle_control_flow_ops(dag) # Clear collected blocks and runs as they are no longer valid after consolidation @@ -195,38 +128,15 @@ def _handle_control_flow_ops(self, dag): pass_manager = PassManager() if "run_list" in self.property_set: pass_manager.append(Collect1qRuns()) - if "block_list" in self.property_set: pass_manager.append(Collect2qBlocks()) pass_manager.append(self) - for node in dag.op_nodes(): - if node.name not in CONTROL_FLOW_OP_NAMES: - continue - dag.substitute_node( - node, - node.op.replace_blocks(pass_manager.run(block) for block in node.op.blocks), - propagate_condition=False, - ) + control_flow_nodes = dag.control_flow_op_nodes() + if control_flow_nodes is not None: + for node in control_flow_nodes: + dag.substitute_node( + node, + node.op.replace_blocks(pass_manager.run(block) for block in node.op.blocks), + propagate_condition=False, + ) return dag - - def _check_not_in_basis(self, dag, gate_name, qargs): - if self.target is not None: - return not self.target.instruction_supported( - gate_name, tuple(dag.find_bit(qubit).index for qubit in qargs) - ) - else: - return self.basis_gates and gate_name not in self.basis_gates - - def _block_qargs_to_indices(self, dag, block_qargs): - """Map each qubit in block_qargs to its wire position among the block's wires. - Args: - block_qargs (list): list of qubits that a block acts on - global_index_map (dict): mapping from each qubit in the - circuit to its wire position within that circuit - Returns: - dict: mapping from qarg to position in block - """ - block_indices = [dag.find_bit(q).index for q in block_qargs] - ordered_block_indices = {bit: index for index, bit in enumerate(sorted(block_indices))} - block_positions = {q: ordered_block_indices[dag.find_bit(q).index] for q in block_qargs} - return block_positions diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index 9301588c0744..8c928a47f69d 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -40,7 +40,6 @@ from qiskit.transpiler.passes.optimization import ( Optimize1qGatesDecomposition, CommutativeCancellation, - Collect2qBlocks, ConsolidateBlocks, InverseCancellation, ) @@ -176,7 +175,6 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana ) ) init.append(CommutativeCancellation()) - init.append(Collect2qBlocks()) init.append(ConsolidateBlocks()) # If approximation degree is None that indicates a request to approximate up to the # error rates in the target. However, in the init stage we don't yet know the target @@ -590,7 +588,6 @@ def _opt_control(property_set): elif optimization_level == 3: # Steps for optimization level 3 _opt = [ - Collect2qBlocks(), ConsolidateBlocks( basis_gates=pass_manager_config.basis_gates, target=pass_manager_config.target, @@ -634,7 +631,6 @@ def _unroll_condition(property_set): elif optimization_level == 2: optimization.append( [ - Collect2qBlocks(), ConsolidateBlocks( basis_gates=pass_manager_config.basis_gates, target=pass_manager_config.target, diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 25d21880bd23..c9bcc9a7904c 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -24,9 +24,9 @@ from qiskit.transpiler.passes import Error from qiskit.transpiler.passes import BasisTranslator from qiskit.transpiler.passes import Unroll3qOrMore -from qiskit.transpiler.passes import Collect2qBlocks -from qiskit.transpiler.passes import Collect1qRuns from qiskit.transpiler.passes import ConsolidateBlocks +from qiskit.transpiler.passes import Collect1qRuns +from qiskit.transpiler.passes import Collect2qBlocks from qiskit.transpiler.passes import UnitarySynthesis from qiskit.transpiler.passes import HighLevelSynthesis from qiskit.transpiler.passes import CheckMap diff --git a/releasenotes/notes/rust-consolidation-a791a00380fc78b8.yaml b/releasenotes/notes/rust-consolidation-a791a00380fc78b8.yaml new file mode 100644 index 000000000000..c84128888951 --- /dev/null +++ b/releasenotes/notes/rust-consolidation-a791a00380fc78b8.yaml @@ -0,0 +1,13 @@ +--- +features_transpiler: + - | + The :class:`.ConsolidateGates` pass will now run the equivalent of the + :class:`.Collect2qBlocks` pass internally if it was not run in a pass + manager prior to the pass. Previously it was required that + :class:`.Collect2qBlocks` or :class:`.Collect1qRuns` were run prior to + :class:`.ConsolidateBlocks` for :class:`.ConsolidateBlocks` to do + anything. By doing the collection internally the overhead of the pass + is reduced. If :class:`.Collect2qBlocks` or :class:`.Collect1qRuns` are + run prior to :class:`.ConsolidateBlocks` the collected runs by those + passes from the property set are used and there is no change in behavior + for the pass. diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index ce88f49560ed..d98bd18b6cbd 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -36,7 +36,7 @@ from qiskit.quantum_info import random_unitary from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.transpiler.preset_passmanagers import level0, level1, level2, level3 -from qiskit.transpiler.passes import Collect2qBlocks, GatesInBasis +from qiskit.transpiler.passes import ConsolidateBlocks, GatesInBasis from qiskit.transpiler.preset_passmanagers.builtin_plugins import OptimizationPassManager from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -270,16 +270,16 @@ def test_unroll_only_if_not_gates_in_basis(self): ) qv_circuit = QuantumVolume(3) gates_in_basis_true_count = 0 - collect_2q_blocks_count = 0 + consolidate_blocks_count = 0 # pylint: disable=unused-argument def counting_callback_func(pass_, dag, time, property_set, count): nonlocal gates_in_basis_true_count - nonlocal collect_2q_blocks_count + nonlocal consolidate_blocks_count if isinstance(pass_, GatesInBasis) and property_set["all_gates_in_basis"]: gates_in_basis_true_count += 1 - if isinstance(pass_, Collect2qBlocks): - collect_2q_blocks_count += 1 + if isinstance(pass_, ConsolidateBlocks): + consolidate_blocks_count += 1 transpile( qv_circuit, @@ -288,7 +288,7 @@ def counting_callback_func(pass_, dag, time, property_set, count): callback=counting_callback_func, translation_method="synthesis", ) - self.assertEqual(gates_in_basis_true_count + 2, collect_2q_blocks_count) + self.assertEqual(gates_in_basis_true_count + 2, consolidate_blocks_count) @ddt