Skip to content

Commit cf7d8b0

Browse files
committed
implement review comments
1 parent 551cd8e commit cf7d8b0

5 files changed

Lines changed: 65 additions & 99 deletions

File tree

crates/accelerate/src/circuit_library/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ mod entanglement;
1616
mod pauli_evolution;
1717
mod pauli_feature_map;
1818
mod quantum_volume;
19-
mod utils;
2019

2120
pub fn circuit_library(m: &Bound<PyModule>) -> PyResult<()> {
2221
m.add_wrapped(wrap_pyfunction!(pauli_evolution::py_pauli_evolution))?;

crates/accelerate/src/circuit_library/pauli_evolution.rs

Lines changed: 61 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,11 @@
1313
use pyo3::prelude::*;
1414
use pyo3::types::{PyList, PyString, PyTuple};
1515
use qiskit_circuit::circuit_data::CircuitData;
16-
use qiskit_circuit::operations::{multiply_param, radd_param, Param, StandardGate};
16+
use qiskit_circuit::operations::{multiply_param, radd_param, Param, PyInstruction, StandardGate};
1717
use qiskit_circuit::packed_instruction::PackedOperation;
18-
use qiskit_circuit::{Clbit, Qubit};
18+
use qiskit_circuit::{imports, Clbit, Qubit};
1919
use smallvec::{smallvec, SmallVec};
2020

21-
use crate::circuit_library::utils;
22-
2321
// custom types for a more readable code
2422
type StandardInstruction = (StandardGate, SmallVec<[Param; 3]>, SmallVec<[Qubit; 2]>);
2523
type Instruction = (
@@ -115,7 +113,7 @@ fn two_qubit_evolution<'a>(
115113
"zx" => Box::new(std::iter::once((StandardGate::RZXGate, param, qubits))),
116114
"yy" => Box::new(std::iter::once((StandardGate::RYYGate, param, qubits))),
117115
"zz" => Box::new(std::iter::once((StandardGate::RZZGate, param, qubits))),
118-
// note that the CX modes (do_fountain=true/false) give the same circuit for a 2-qubit
116+
// Note: the CX modes (do_fountain=true/false) give the same circuit for a 2-qubit
119117
// Pauli, so we just set it to false here
120118
_ => Box::new(multi_qubit_evolution(pauli, indices, time, false, false)),
121119
}
@@ -135,22 +133,25 @@ fn multi_qubit_evolution(
135133
.collect();
136134

137135
// get the basis change: x -> HGate, y -> SXdgGate, z -> nothing
138-
let basis_change = active_paulis
139-
.clone()
140-
.into_iter()
136+
let basis_change: Vec<StandardInstruction> = active_paulis
137+
.iter()
141138
.filter(|(p, _)| *p != 'z')
142139
.map(|(p, q)| match p {
143-
'x' => (StandardGate::HGate, smallvec![], smallvec![q]),
144-
'y' => (StandardGate::SXdgGate, smallvec![], smallvec![q]),
140+
'x' => (StandardGate::HGate, smallvec![], smallvec![q.clone()]),
141+
'y' => (StandardGate::SXdgGate, smallvec![], smallvec![q.clone()]),
145142
_ => unreachable!("Invalid Pauli string."), // "z" and "i" have been filtered out
146-
});
143+
})
144+
.collect();
147145

148146
// get the inverse basis change
149-
let inverse_basis_change = basis_change.clone().map(|(gate, _, qubit)| match gate {
150-
StandardGate::HGate => (gate, smallvec![], qubit),
151-
StandardGate::SXdgGate => (StandardGate::SXGate, smallvec![], qubit),
152-
_ => unreachable!("Invalid basis-changing Clifford."),
153-
});
147+
let inverse_basis_change: Vec<StandardInstruction> = basis_change
148+
.iter()
149+
.map(|(gate, _, qubit)| match gate {
150+
StandardGate::HGate => (StandardGate::HGate, smallvec![], qubit.clone()),
151+
StandardGate::SXdgGate => (StandardGate::SXGate, smallvec![], qubit.clone()),
152+
_ => unreachable!("Invalid basis-changing Clifford."),
153+
})
154+
.collect();
154155

155156
// get the CX propagation up to the first qubit, and down
156157
let (chain_up, chain_down) = match do_fountain {
@@ -178,6 +179,7 @@ fn multi_qubit_evolution(
178179

179180
// and finally chain everything together
180181
basis_change
182+
.into_iter()
181183
.chain(chain_down)
182184
.chain(z_rotation)
183185
.chain(chain_up)
@@ -215,42 +217,39 @@ fn multi_qubit_evolution(
215217
/// Returns:
216218
/// Circuit data for to implement the evolution.
217219
#[pyfunction]
218-
#[pyo3(signature = (num_qubits, sparse_paulis, insert_barriers=false, do_fountain=false))]
220+
#[pyo3(name = "pauli_evolution", signature = (num_qubits, sparse_paulis, insert_barriers=false, do_fountain=false))]
219221
pub fn py_pauli_evolution(
220-
py: Python,
221222
num_qubits: i64,
222223
sparse_paulis: &Bound<PyList>,
223224
insert_barriers: bool,
224225
do_fountain: bool,
225226
) -> PyResult<CircuitData> {
227+
let py = sparse_paulis.py();
226228
let num_paulis = sparse_paulis.len();
227229
let mut paulis: Vec<String> = Vec::with_capacity(num_paulis);
228230
let mut indices: Vec<Vec<u32>> = Vec::with_capacity(num_paulis);
229231
let mut times: Vec<Param> = Vec::with_capacity(num_paulis);
230232
let mut global_phase = Param::Float(0.0);
233+
let mut modified_phase = false; // keep track of whether we modified the phase
231234

232235
for el in sparse_paulis.iter() {
233236
let tuple = el.downcast::<PyTuple>()?;
234237
let pauli = tuple.get_item(0)?.downcast::<PyString>()?.to_string();
235238
let time = Param::extract_no_coerce(&tuple.get_item(2)?)?;
236239

237240
if pauli.as_str().chars().all(|p| p == 'i') {
238-
global_phase = radd_param(global_phase, time, py); // apply factor -1 at the end
241+
global_phase = radd_param(global_phase, time, py);
242+
modified_phase = true;
239243
continue;
240244
}
241245

242246
paulis.push(pauli);
243247
times.push(time); // note we do not multiply by 2 here, this is done Python side!
244-
indices.push(
245-
tuple
246-
.get_item(1)?
247-
.downcast::<PyList>()?
248-
.iter()
249-
.map(|index| index.extract::<u32>())
250-
.collect::<PyResult<_>>()?,
251-
);
248+
indices.push(tuple.get_item(1)?.extract::<Vec<u32>>()?)
252249
}
253250

251+
let barrier = get_barrier(py, num_qubits as u32);
252+
254253
let evos = paulis.iter().enumerate().zip(indices).zip(times).flat_map(
255254
|(((i, pauli), qubits), time)| {
256255
let as_packed = pauli_evolution(pauli, qubits, time, false, do_fountain).map(
@@ -263,16 +262,23 @@ pub fn py_pauli_evolution(
263262
))
264263
},
265264
);
266-
as_packed.chain(utils::maybe_barrier(
267-
py,
268-
num_qubits as u32,
269-
insert_barriers && i < (num_paulis - 1), // do not add barrier on final block
270-
))
265+
266+
// this creates an iterator containing a barrier only if required, otherwise it is empty
267+
let maybe_barrier = (insert_barriers && i < (num_paulis - 1))
268+
.then_some(Ok(barrier.clone()))
269+
.into_iter();
270+
as_packed.chain(maybe_barrier)
271271
},
272272
);
273273

274-
// apply factor -1 for global phase
275-
global_phase = multiply_param(&global_phase, -1.0, py);
274+
// When handling all-identity Paulis above, we added the time as global phase.
275+
// However, the all-identity Paulis should add a negative phase, as they implement
276+
// exp(-i t I). We apply the negative sign here, to only do a single (-1) multiplication,
277+
// instead of doing it every time we find an all-identity Pauli.
278+
if modified_phase {
279+
global_phase = multiply_param(&global_phase, -1.0, py);
280+
}
281+
276282
CircuitData::from_packed_operations(py, num_qubits as u32, 0, evos, global_phase)
277283
}
278284

@@ -301,3 +307,24 @@ fn cx_fountain(
301307
)
302308
}))
303309
}
310+
311+
fn get_barrier(py: Python, num_qubits: u32) -> Instruction {
312+
let barrier_cls = imports::BARRIER.get_bound(py);
313+
let barrier = barrier_cls
314+
.call1((num_qubits,))
315+
.expect("Could not create Barrier Python-side");
316+
let barrier_inst = PyInstruction {
317+
qubits: num_qubits,
318+
clbits: 0,
319+
params: 0,
320+
op_name: "barrier".to_string(),
321+
control_flow: false,
322+
instruction: barrier.into(),
323+
};
324+
(
325+
barrier_inst.into(),
326+
smallvec![],
327+
(0..num_qubits).map(Qubit).collect(),
328+
vec![],
329+
)
330+
}

crates/accelerate/src/circuit_library/utils.rs

Lines changed: 0 additions & 58 deletions
This file was deleted.

qiskit/synthesis/evolution/product_formula.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from qiskit.circuit.quantumcircuit import QuantumCircuit, ParameterValueType
2323
from qiskit.quantum_info import SparsePauliOp, Pauli
2424
from qiskit.utils.deprecation import deprecate_arg
25-
from qiskit._accelerate.circuit_library import py_pauli_evolution
25+
from qiskit._accelerate.circuit_library import pauli_evolution
2626

2727
from .evolution_synthesis import EvolutionSynthesis
2828

@@ -151,9 +151,7 @@ def synthesize(self, evolution: PauliEvolutionGate) -> QuantumCircuit:
151151
else:
152152
# this is the fast path, where the whole evolution is constructed Rust-side
153153
cx_fountain = self._cx_structure == "fountain"
154-
data = py_pauli_evolution(
155-
num_qubits, pauli_rotations, self.insert_barriers, cx_fountain
156-
)
154+
data = pauli_evolution(num_qubits, pauli_rotations, self.insert_barriers, cx_fountain)
157155
circuit = QuantumCircuit._from_circuit_data(data, add_regs=True)
158156

159157
return circuit
@@ -210,7 +208,7 @@ def _custom_evolution(self, num_qubits, pauli_rotations):
210208
local_pauli = (pauli_string, list(range(len(qubits))), coeff)
211209

212210
# build the circuit Rust-side
213-
data = py_pauli_evolution(
211+
data = pauli_evolution(
214212
len(qubits),
215213
[local_pauli],
216214
False,

releasenotes/notes/pauli-evo-plugins-612850146c3f7d49.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
features_quantum_info:
33
- |
44
Added :meth:`.SparsePauliOperator.to_sparse_list` to convert an operator into
5-
a sparse list format. This works analogous to :meth:`.SparsePauliOperator.from_sparse_list`.
5+
a sparse list format. This works inversely to :meth:`.SparsePauliOperator.from_sparse_list`.
66
For example::
77
88
from qiskit.quantum_info import SparsePauliOp

0 commit comments

Comments
 (0)