Skip to content

Commit eab94f2

Browse files
authored
Remove qiskit_circuit from QI matrix composition and Clifford (#15907)
This is the beginning of a thread of work to separate out dependence on `qiskit-circuit` from `qiskit-quantum-information`, to better allow the base types in the QI crate to be embedded in first-part circuit IR types. In general, the definitions of the quantum-information objects will remain in `qiskit-quantum-information`. First-party core-IR attributes of them that related to circuit objects will move to `qiskit-circuit`, while general circuit-level synthesis will move to `qiskit-synthesis`.
1 parent 25a8160 commit eab94f2

7 files changed

Lines changed: 39 additions & 32 deletions

File tree

crates/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ The intention is that (much) longer term, we might be wanting to expose more of
2323
This depends on `qiskit-circuit`, but is otherwise pretty standalone, and it's unlikely that other things will need to interact with it.
2424
* `qiskit-qasm3` is the Qiskit-specific side of the OpenQASM 3 importer.
2525
The actual parser lives at https://github.com/Qiskit/openqasm3_parser, and is its own set of Rust-only crates.
26-
* `qiskit-quantum-info` is the crate for quantum information.
26+
* `qiskit-quantum-info` contains definitions and operations on quantum-information-related objects,
27+
without reference to circuit-level objects. This is a base crate. Interactions between
28+
circuit-level objects and quantum-info objects are either in `qiskit-circuit` (if they are part of
29+
core IR definitions) or `qiskit_synthesis::quantum_info` (if not).
2730
* `qiskit-synthesis` is the crate for synthesis functionality.
2831
* `qiskit-circuit-library` is the crate for circuit library functions. It contains constructors or other
2932
circuit functionality that builds on the core circuit data model defined in `qiskit-circuit`.

crates/quantum_info/src/clifford.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use std::fmt;
1313

1414
use fixedbitset::FixedBitSet;
1515
use ndarray::{Array2, ArrayView2};
16-
use qiskit_circuit::Qubit;
1716

1817
/// Symplectic matrix.
1918
pub struct SymplecticMatrix {
@@ -291,7 +290,7 @@ impl Clifford {
291290

292291
/// Evolving the single-qubit Pauli-Z with Z on qubit qbit.
293292
/// Returns the evolved Pauli in the a sparse ZX format: (sign, z, x, indices).
294-
pub fn get_inverse_z(&self, qbit: usize) -> (bool, Vec<bool>, Vec<bool>, Vec<Qubit>) {
293+
pub fn get_inverse_z(&self, qbit: usize) -> (bool, Vec<bool>, Vec<bool>, Vec<usize>) {
295294
let mut z = Vec::with_capacity(self.num_qubits);
296295
let mut x = Vec::with_capacity(self.num_qubits);
297296
let mut indices = Vec::with_capacity(self.num_qubits);
@@ -304,7 +303,7 @@ impl Clifford {
304303
if z_bit || x_bit {
305304
z.push(z_bit);
306305
x.push(x_bit);
307-
indices.push(Qubit::new(i));
306+
indices.push(i);
308307
if x_bit {
309308
pauli_indices.push(i);
310309
}

crates/quantum_info/src/unitary_compose.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use ndarray::{Array, Array2, ArrayView, ArrayView2, IxDyn};
1414
use ndarray_einsum::*;
1515
use num_complex::{Complex, Complex64};
16-
use qiskit_circuit::Qubit;
1716

1817
static LOWERCASE: [u8; 26] = [
1918
b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', b'n', b'o', b'p',
@@ -30,13 +29,13 @@ static _UPPERCASE: [u8; 26] = [
3029
pub fn compose(
3130
gate_unitary: &ArrayView2<Complex64>,
3231
overall_unitary: &ArrayView2<Complex64>,
33-
qubits: &[Qubit],
32+
indices: &[usize],
3433
front: bool,
3534
) -> Result<Array2<Complex64>, &'static str> {
3635
let gate_qubits = gate_unitary.shape()[0].ilog2() as usize;
3736

3837
// Full composition of operators
39-
if qubits.is_empty() {
38+
if indices.is_empty() {
4039
if front {
4140
return Ok(gate_unitary.dot(overall_unitary));
4241
} else {
@@ -54,9 +53,9 @@ pub fn compose(
5453
//product, which is the last tensor wire index.
5554
let tensor = per_qubit_shaped(gate_unitary);
5655
let mat = per_qubit_shaped(overall_unitary);
57-
let indices = qubits
56+
let indices = indices
5857
.iter()
59-
.map(|q| num_indices - 1 - q.index())
58+
.map(|i| num_indices - 1 - i)
6059
.collect::<Vec<usize>>();
6160
let num_rows = usize::pow(2, num_indices as u32);
6261

@@ -173,11 +172,11 @@ fn _ind(i: usize, reversed: bool) -> usize {
173172
pub fn matmul_2q(
174173
left: &ArrayView2<Complex64>,
175174
right: &ArrayView2<Complex64>,
176-
qargs: &[Qubit],
175+
indices: &[usize],
177176
) -> Array2<Complex64> {
178177
let mut out = Array2::zeros((4, 4));
179178

180-
let rev = qargs[0].0 == 1;
179+
let rev = indices[0] == 1;
181180
for i in 0..4usize {
182181
for j in 0..4usize {
183182
for k in 0..4usize {

crates/synthesis/src/matrix/sim.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ pub fn sim_unitary_circuit(circuit: &CircuitData) -> Result<Array2<Complex64>, S
4848
// Product matrix holding the result
4949
let mut product_mat: Array2<Complex64> =
5050
Array2::<Complex64>::eye(2_usize.pow(num_qubits as u32)) * global_phase_exp;
51+
let mut qubits = Vec::new();
5152

5253
for inst in circuit.data() {
5354
if !circuit.get_cargs(inst.clbits).is_empty() {
@@ -61,13 +62,14 @@ pub fn sim_unitary_circuit(circuit: &CircuitData) -> Result<Array2<Complex64>, S
6162
continue;
6263
}
6364

64-
let qubits = circuit.get_qargs(inst.qubits);
65+
qubits.clear();
66+
qubits.extend(circuit.get_qargs(inst.qubits).iter().map(|q| q.index()));
6567

6668
let mat = inst
6769
.try_matrix()
6870
.ok_or_else(|| format!("Cannot extract matrix for operation {:?}.", inst.op.name()))?;
6971

70-
product_mat = unitary_compose::compose(&product_mat.view(), &mat.view(), qubits, false)?;
72+
product_mat = unitary_compose::compose(&product_mat.view(), &mat.view(), &qubits, false)?;
7173
}
7274

7375
Ok(product_mat)

crates/transpiler/src/commutation_checker.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -679,10 +679,13 @@ impl CommutationChecker {
679679
}
680680
}
681681

682-
let first_qarg: Vec<Qubit> = Vec::from_iter((0..first_qargs.len() as u32).map(Qubit));
683-
let second_qarg: Vec<Qubit> = second_qargs.iter().map(|q| qarg[q]).collect();
682+
let first_indices = Vec::from_iter(0..first_qargs.len());
683+
let second_indices = second_qargs
684+
.iter()
685+
.map(|q| qarg[q].index())
686+
.collect::<Vec<_>>();
684687

685-
if first_qarg.len() > second_qarg.len() {
688+
if first_indices.len() > second_indices.len() {
686689
return Err(CommutationError::FirstInstructionTooLarge);
687690
};
688691
let first_mat = match try_matrix_with_definition(first_op, first_params, None) {
@@ -701,7 +704,7 @@ impl CommutationChecker {
701704
// 2. This code here expands the first op to match the second -- hence we always
702705
// match the operator sizes.
703706
// This whole extension logic could be avoided since we know the second one is larger.
704-
let extra_qarg2 = num_qubits - first_qarg.len() as u32;
707+
let extra_qarg2 = num_qubits - first_indices.len() as u32;
705708
let first_mat = if extra_qarg2 > 0 {
706709
let id_op = Array2::<Complex64>::eye(usize::pow(2, extra_qarg2));
707710
kron(&id_op, &first_mat)
@@ -715,7 +718,7 @@ impl CommutationChecker {
715718
let op12 = match unitary_compose::compose(
716719
&first_mat.view(),
717720
&second_mat.view(),
718-
&second_qarg,
721+
&second_indices,
719722
false,
720723
) {
721724
Ok(matrix) => matrix,
@@ -724,13 +727,13 @@ impl CommutationChecker {
724727
let op21 = match unitary_compose::compose(
725728
&first_mat.view(),
726729
&second_mat.view(),
727-
&second_qarg,
730+
&second_indices,
728731
true,
729732
) {
730733
Ok(matrix) => matrix,
731734
Err(e) => return Err(CommutationError::EinsumError(e)),
732735
};
733-
let (fid, phase) = gate_metrics::gate_fidelity(&op12.view(), &op21.view(), None);
736+
let (fid, phase) = gate_metrics::gate_fidelity(&op12.view(), &op21.view());
734737

735738
// we consider the gates as commuting if the process fidelity of
736739
// AB (BA)^\dagger is approximately the identity and there is no global phase difference

crates/transpiler/src/gate_metrics.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
use ndarray::ArrayView2;
1414
use num_complex::{Complex64, ComplexFloat};
15-
use qiskit_circuit::{Qubit, operations::StandardGate};
15+
use qiskit_circuit::operations::StandardGate;
1616

1717
use qiskit_quantum_info::unitary_compose;
1818

@@ -46,19 +46,13 @@ pub fn rotation_trace_and_dim(rotation: StandardGate, angle: f64) -> Option<(Com
4646
Some((trace_over_dim, dim))
4747
}
4848

49-
pub fn gate_fidelity(
50-
left: &ArrayView2<Complex64>,
51-
right: &ArrayView2<Complex64>,
52-
qargs: Option<&[Qubit]>,
53-
) -> (f64, f64) {
49+
pub fn gate_fidelity(left: &ArrayView2<Complex64>, right: &ArrayView2<Complex64>) -> (f64, f64) {
5450
let dim = left.nrows();
5551

5652
let left = left.t().mapv(|el| el.conj());
5753
let product = match dim {
5854
2 => unitary_compose::matmul_1q(&left.view(), right),
59-
4 => {
60-
unitary_compose::matmul_2q(&left.view(), right, qargs.unwrap_or(&[Qubit(0), Qubit(1)]))
61-
}
55+
4 => unitary_compose::matmul_2q(&left.view(), right, &[0, 1]),
6256
_ => left.dot(right),
6357
};
6458
let trace = product.diag().sum();

crates/transpiler/src/passes/litinski_transformation.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ pub fn run_litinski_transformation(
8686
let num_qubits = dag.num_qubits();
8787
let mut clifford = Clifford::identity(num_qubits);
8888

89+
let mut qargs = Vec::new();
90+
8991
// Keep track of the update to the global phase (produced when converting T/Tdg gates
9092
// to RZ-rotations).
9193
let mut global_phase_update = Param::Float(0.);
@@ -251,6 +253,8 @@ pub fn run_litinski_transformation(
251253
// where signs `true` and `false` correspond to coefficients `-1` and `+1` respectively.
252254
let (sign, z, x, indices) =
253255
clifford.get_inverse_z(dag.get_qargs(inst.qubits)[0].index());
256+
qargs.clear();
257+
qargs.extend(indices.iter().map(|i| Qubit::new(*i)));
254258

255259
// In the legacy path, we add PauliEvolutionGate as rotation gates, otherwise
256260
// we add PauliProductRotation. The new path should not call Python at any
@@ -279,7 +283,7 @@ pub fn run_litinski_transformation(
279283
.get_bound(py)
280284
.call1((obs, time.clone()))?;
281285
Ok(PyOperationTypes::Gate(PyInstruction {
282-
qubits: indices.len() as u32,
286+
qubits: qargs.len() as u32,
283287
clbits: 0,
284288
params: 1,
285289
op_name: "PauliEvolution".to_string(),
@@ -291,7 +295,7 @@ pub fn run_litinski_transformation(
291295

292296
new_dag.apply_operation_back(
293297
packed_op,
294-
&indices,
298+
&qargs,
295299
&[],
296300
Some(Parameters::Params(smallvec![param])),
297301
None,
@@ -304,13 +308,16 @@ pub fn run_litinski_transformation(
304308
// where signs `true` and `false` correspond to coefficients `-1` and `+1` respectively.
305309
let (sign, z, x, indices) =
306310
clifford.get_inverse_z(dag.get_qargs(inst.qubits)[0].index());
311+
qargs.clear();
312+
qargs.extend(indices.iter().map(|i| Qubit::new(*i)));
313+
307314
let ppm = PauliProductMeasurement { z, x, neg: sign };
308315

309316
let ppm_clbits = dag.get_cargs(inst.clbits);
310317

311318
new_dag.apply_operation_back(
312319
PauliBased::PauliProductMeasurement(ppm).into(),
313-
&indices,
320+
&qargs,
314321
ppm_clbits,
315322
None,
316323
None,

0 commit comments

Comments
 (0)