Skip to content

Commit f7585e2

Browse files
mtreinishgadial
authored andcommitted
Use nalgebra matrices for uncommon paths in consolidate blocks (#15881)
In the recently merged #15871 we updated the consolidate blocks pass to use Matrix4 in the common path of a 2q block being consolidated. This is like > 90% of what the pass does when run in the preset pass manager. However, there were uncommon cases in the pass around the handling of blocks of a single gate that are outside of the target which were not updated to use nalgebra arrays if it's a fixed size 1q or 2q gate. This commit updates these uncommon paths so that we're always returning an nalgebra matrix in the output UnitaryGate if the block being consolidated is a single qubit or two qubits.
1 parent 73c359b commit f7585e2

2 files changed

Lines changed: 72 additions & 12 deletions

File tree

crates/quantum_info/src/convert_2q_block_matrix.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use pyo3::prelude::*;
1616

1717
use num_complex::Complex64;
1818
use numpy::PyReadonlyArray2;
19-
use numpy::nalgebra::{Matrix4, MatrixViewMut4};
19+
use numpy::nalgebra::{Matrix2, Matrix4, MatrixViewMut4};
2020
use numpy::ndarray::{Array2, ArrayView2};
2121
use rustworkx_core::petgraph::stable_graph::NodeIndex;
2222

@@ -52,6 +52,16 @@ fn matrix4_from_pyreadonly(array: &PyReadonlyArray2<Complex64>) -> Matrix4<Compl
5252
)
5353
}
5454

55+
#[inline]
56+
fn matrix2_from_pyreadonly(array: &PyReadonlyArray2<Complex64>) -> Matrix2<Complex64> {
57+
Matrix2::new(
58+
*array.get((0, 0)).unwrap(),
59+
*array.get((0, 1)).unwrap(),
60+
*array.get((1, 0)).unwrap(),
61+
*array.get((1, 1)).unwrap(),
62+
)
63+
}
64+
5565
#[inline]
5666
pub fn get_matrix_from_inst(inst: &PackedInstruction) -> PyResult<Array2<Complex64>> {
5767
if let Some(mat) = inst.try_matrix() {
@@ -109,6 +119,34 @@ pub fn get_2q_matrix_from_inst(inst: &PackedInstruction) -> PyResult<Matrix4<Com
109119
})
110120
}
111121

122+
#[inline]
123+
pub fn get_1q_matrix_from_inst(inst: &PackedInstruction) -> PyResult<Matrix2<Complex64>> {
124+
if let Some(mat) = inst.try_matrix_as_nalgebra_1q() {
125+
return Ok(mat);
126+
}
127+
if inst.op.try_standard_gate().is_some() {
128+
return Err(QiskitError::new_err(
129+
"Parameterized gates can't be consolidated",
130+
));
131+
}
132+
let OperationRef::Gate(gate) = inst.op.view() else {
133+
return Err(QiskitError::new_err(
134+
"Can't compute matrix of non-unitary op",
135+
));
136+
};
137+
// If the operation is a custom python gate, we will acquire the gil and use an
138+
// Operator. Otherwise, using op.matrix() should work. A user should not be
139+
// able to reach this condition in Rust standalone mode.
140+
Python::attach(|py| {
141+
let res = QI_OPERATOR
142+
.get_bound(py)
143+
.call1((gate.instruction.clone_ref(py),))?
144+
.getattr(intern!(py, "data"))?
145+
.extract()?;
146+
Ok(matrix2_from_pyreadonly(&res))
147+
})
148+
}
149+
112150
/// Quaternion-based collect of two parallel runs of 1q gates.
113151
#[derive(Clone, Debug)]
114152
struct Separable1q {

crates/transpiler/src/passes/consolidate_blocks.rs

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ use qiskit_circuit::interner::Interned;
3131
use qiskit_circuit::operations::StandardGate;
3232
use qiskit_circuit::operations::{ArrayType, Operation, Param, UnitaryGate};
3333
use qiskit_circuit::packed_instruction::PackedOperation;
34-
use qiskit_quantum_info::convert_2q_block_matrix::{blocks_to_matrix, get_matrix_from_inst};
34+
use qiskit_quantum_info::convert_2q_block_matrix::{
35+
blocks_to_matrix, get_1q_matrix_from_inst, get_2q_matrix_from_inst, get_matrix_from_inst,
36+
};
3537
use qiskit_synthesis::linalg::nalgebra_array_view;
3638
use qiskit_synthesis::two_qubit_decompose::RXXEquivalent;
3739
use qiskit_synthesis::two_qubit_decompose::{
@@ -289,14 +291,31 @@ fn py_run_consolidate_blocks(
289291
phys_qargs.get(dag, inst.qubits),
290292
) {
291293
all_block_gates.insert(inst_node);
292-
let matrix = match get_matrix_from_inst(inst) {
293-
Ok(mat) => mat,
294-
Err(_) => continue,
295-
};
296-
// TODO: Use Matrix2/ArrayType::OneQ when we're using nalgebra
297-
// for consolidation
298-
let unitary_gate = UnitaryGate {
299-
array: ArrayType::NDArray(matrix),
294+
let num_qubits = inst.op.num_qubits();
295+
let unitary_gate = if num_qubits == 1 {
296+
let matrix = match get_1q_matrix_from_inst(inst) {
297+
Ok(mat) => mat,
298+
Err(_) => continue,
299+
};
300+
UnitaryGate {
301+
array: ArrayType::OneQ(matrix),
302+
}
303+
} else if num_qubits == 2 {
304+
let matrix = match get_2q_matrix_from_inst(inst) {
305+
Ok(mat) => mat,
306+
Err(_) => continue,
307+
};
308+
UnitaryGate {
309+
array: ArrayType::TwoQ(matrix),
310+
}
311+
} else {
312+
let matrix = match get_matrix_from_inst(inst) {
313+
Ok(mat) => mat,
314+
Err(_) => continue,
315+
};
316+
UnitaryGate {
317+
array: ArrayType::NDArray(matrix),
318+
}
300319
};
301320
dag.substitute_op(
302321
inst_node,
@@ -450,12 +469,15 @@ fn py_run_consolidate_blocks(
450469
first_qubits,
451470
)
452471
{
453-
let matrix = match get_matrix_from_inst(first_inst) {
472+
// Runs are necessarily single qubit gates so if it has a matrix then it
473+
// must have a single qubit matrix or it's not a run and the blocks handling above
474+
// would have covered it
475+
let matrix = match get_1q_matrix_from_inst(first_inst) {
454476
Ok(mat) => mat,
455477
Err(_) => continue,
456478
};
457479
let unitary_gate = UnitaryGate {
458-
array: ArrayType::NDArray(matrix),
480+
array: ArrayType::OneQ(matrix),
459481
};
460482
dag.substitute_op(
461483
first_inst_node,

0 commit comments

Comments
 (0)