Skip to content

Commit 09af631

Browse files
Support phase gates in LitinskiTransformation (#15773)
* Support phase gates in `LitinskiTransformation` Supporting phase gates is straightforward and there's no reason we would require the compiler to first map to RZ. This commit does not tackle extensions of the Litinski transformation code to generic rotations, which is something we should add in the soon future. * clippy & use predefined constants * Sync after backward compat fix --------- Co-authored-by: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com>
1 parent 0db6650 commit 09af631

4 files changed

Lines changed: 46 additions & 17 deletions

File tree

crates/transpiler/src/passes/litinski_transformation.rs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use qiskit_circuit::imports::PAULI_EVOLUTION_GATE;
1717
use qiskit_circuit::instruction::Parameters;
1818
use qiskit_circuit::operations::{
1919
Operation, OperationRef, Param, PauliBased, PauliProductMeasurement, PauliProductRotation,
20-
PyInstruction, PyOperationTypes, StandardGate, StandardInstruction, multiply_param,
20+
PyInstruction, PyOperationTypes, StandardGate, StandardInstruction, multiply_param, radd_param,
2121
};
2222
use qiskit_circuit::packed_instruction::PackedInstruction;
2323
use qiskit_circuit::{BlocksMode, Qubit, VarsMode};
@@ -28,18 +28,18 @@ use qiskit_quantum_info::clifford::Clifford;
2828
use qiskit_quantum_info::sparse_observable::{BitTerm, SparseObservable};
2929

3030
use smallvec::smallvec;
31-
use std::f64::consts::PI;
31+
use std::f64::consts::{FRAC_PI_4, FRAC_PI_8};
3232

3333
// List of gate/instruction names supported by the pass: the pass raises an error if the circuit
3434
// contains instruction with names outside of this list.
35-
static SUPPORTED_INSTRUCTION_NAMES: [&str; 20] = [
35+
static SUPPORTED_INSTRUCTION_NAMES: [&str; 22] = [
3636
"id", "x", "y", "z", "h", "s", "sdg", "sx", "sxdg", "cx", "cz", "cy", "swap", "iswap", "ecr",
37-
"dcx", "t", "tdg", "rz", "measure",
37+
"dcx", "t", "tdg", "rz", "p", "u1", "measure",
3838
];
3939

4040
// List of instruction names which are modified by the pass: the pass is skipped if the circuit
4141
// contains no instructions with names in this list.
42-
static HANDLED_INSTRUCTION_NAMES: [&str; 4] = ["t", "tdg", "rz", "measure"];
42+
static HANDLED_INSTRUCTION_NAMES: [&str; 6] = ["t", "tdg", "rz", "p", "u1", "measure"];
4343

4444
#[pyfunction]
4545
#[pyo3(signature = (dag, fix_clifford=true, insert_barrier=false, use_ppr=false))]
@@ -88,7 +88,7 @@ pub fn run_litinski_transformation(
8888

8989
// Keep track of the update to the global phase (produced when converting T/Tdg gates
9090
// to RZ-rotations).
91-
let mut global_phase_update = 0.;
91+
let mut global_phase_update = Param::Float(0.);
9292

9393
// Keep track of the clifford operations in the circuit.
9494
let mut clifford_ops: Vec<&PackedInstruction> = Vec::with_capacity(clifford_count);
@@ -218,24 +218,33 @@ pub fn run_litinski_transformation(
218218
}
219219
OperationRef::StandardGate(StandardGate::T)
220220
| OperationRef::StandardGate(StandardGate::Tdg)
221-
| OperationRef::StandardGate(StandardGate::RZ) => {
221+
| OperationRef::StandardGate(StandardGate::RZ)
222+
| OperationRef::StandardGate(StandardGate::Phase)
223+
| OperationRef::StandardGate(StandardGate::U1) => {
222224
// Convert T and Tdg gates to RZ rotations
223225
let (angle, phase_update) = match inst.op.view() {
224226
OperationRef::StandardGate(StandardGate::T) => {
225-
(Param::Float(PI / 4.), PI / 8.)
227+
(Param::Float(FRAC_PI_4), Param::Float(FRAC_PI_8))
226228
}
227229
OperationRef::StandardGate(StandardGate::Tdg) => {
228-
(Param::Float(-PI / 4.0), -PI / 8.)
230+
(Param::Float(-FRAC_PI_4), Param::Float(-FRAC_PI_8))
229231
}
230232
OperationRef::StandardGate(StandardGate::RZ) => {
231233
let param = &inst.params_view()[0];
232-
(param.clone(), 0.)
234+
(param.clone(), Param::Float(0.))
235+
}
236+
OperationRef::StandardGate(StandardGate::Phase)
237+
| OperationRef::StandardGate(StandardGate::U1) => {
238+
let param = &inst.params_view()[0];
239+
(param.clone(), multiply_param(param, 0.5))
233240
}
234241
_ => {
235-
unreachable!("We cannot have gates other than T/Tdg/RZ at this point.");
242+
unreachable!(
243+
"We cannot have gates other than T/Tdg/RZ/P/U1 at this point."
244+
);
236245
}
237246
};
238-
global_phase_update += phase_update;
247+
global_phase_update = radd_param(global_phase_update, phase_update);
239248

240249
// Evolve the single-qubit Pauli-Z with Z on the given qubit.
241250
// Returns the evolved Pauli in the sparse format: (sign, pauli z, pauli x, indices),
@@ -317,7 +326,7 @@ pub fn run_litinski_transformation(
317326
}
318327
}
319328

320-
new_dag.add_global_phase(&Param::Float(global_phase_update))?;
329+
new_dag.add_global_phase(&global_phase_update)?;
321330

322331
// Add Clifford gates to the Qiskit circuit (when required).
323332
// Since we aim to preserve the global phase of the circuit, we add the Clifford operations from

qiskit/transpiler/passes/optimization/litinski_transformation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class LitinskiTransformation(TransformationPass):
3434
3535
The list of supported :math:`R_Z`-rotations is:
3636
37-
``["t", "tdg", "rz"]``
37+
``["t", "tdg", "rz", "p", "u1"]``
3838
3939
Example:
4040
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
features_transpiler:
3+
- The :class:`.LitinskiTransformation` now also supports circuits with
4+
:class:`.PhaseGate` and :class:`.U1Gate` instructions. Previously, the only
5+
accepted parameterized gate was :class:`.RZGate`.

test/python/transpiler/test_litinski_transformation.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
PauliEvolutionGate,
2323
PauliProductMeasurement,
2424
PauliProductRotationGate,
25+
U1Gate,
2526
)
2627
from qiskit.circuit.random import random_clifford_circuit
2728
from qiskit.compiler import transpile
@@ -179,14 +180,15 @@ def test_all_supported_clifford_gates(self):
179180
qc.rz(0.1, 1)
180181
qc.tdg(2)
181182
qc.rz(-0.2, 3)
183+
qc.p(0.3, 0)
184+
qc.append(U1Gate(-0.5), [0])
182185

183186
qc_litinski = LitinskiTransformation()(qc)
184187
ops_litinski = qc_litinski.count_ops()
185188

186189
# make sure the transform was applied
187-
self.assertNotIn("t", ops_litinski)
188-
self.assertNotIn("tdg", ops_litinski)
189-
self.assertNotIn("rz", ops_litinski)
190+
for z_rot in ["t", "tdg", "rz", "p", "u1"]:
191+
self.assertNotIn(z_rot, ops_litinski)
190192

191193
# make sure the result is correct
192194
self.assertEqual(Operator(qc_litinski), Operator(qc))
@@ -254,6 +256,19 @@ def test_tdg(self):
254256

255257
self.assertEqual(qct, expected)
256258

259+
def test_p(self):
260+
"""Test the phase gate, ensuring we got the global phase right."""
261+
angle = 0.231
262+
qc = QuantumCircuit(1)
263+
qc.p(angle, 0)
264+
265+
qct = LitinskiTransformation(use_ppr=True)(qc)
266+
self.assertTrue(np.allclose(Operator(qc).data, Operator(qct).data))
267+
268+
expected = QuantumCircuit(1, global_phase=angle / 2)
269+
expected.append(PauliProductRotationGate(Pauli("Z"), angle), [0])
270+
self.assertEqual(qct, expected)
271+
257272
def test_h_t(self):
258273
"""Test the transform on a circuit with an H-gate and a T-gate."""
259274
qc = QuantumCircuit(2)

0 commit comments

Comments
 (0)