Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions crates/transpiler/src/passes/litinski_transformation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use qiskit_circuit::imports::PAULI_EVOLUTION_GATE;
use qiskit_circuit::instruction::Parameters;
use qiskit_circuit::operations::{
Operation, OperationRef, Param, PauliBased, PauliProductMeasurement, PauliProductRotation,
PyInstruction, PyOperationTypes, StandardGate, StandardInstruction, multiply_param,
PyInstruction, PyOperationTypes, StandardGate, StandardInstruction, multiply_param, radd_param,
};
use qiskit_circuit::packed_instruction::PackedInstruction;
use qiskit_circuit::{BlocksMode, Qubit, VarsMode};
Expand All @@ -28,18 +28,18 @@ use qiskit_quantum_info::clifford::Clifford;
use qiskit_quantum_info::sparse_observable::{BitTerm, SparseObservable};

use smallvec::smallvec;
use std::f64::consts::PI;
use std::f64::consts::{FRAC_PI_4, FRAC_PI_8};

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

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

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

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

// Keep track of the clifford operations in the circuit.
let mut clifford_ops: Vec<&PackedInstruction> = Vec::with_capacity(clifford_count);
Expand Down Expand Up @@ -218,24 +218,33 @@ pub fn run_litinski_transformation(
}
OperationRef::StandardGate(StandardGate::T)
| OperationRef::StandardGate(StandardGate::Tdg)
| OperationRef::StandardGate(StandardGate::RZ) => {
| OperationRef::StandardGate(StandardGate::RZ)
| OperationRef::StandardGate(StandardGate::Phase)
| OperationRef::StandardGate(StandardGate::U1) => {
// Convert T and Tdg gates to RZ rotations
let (angle, phase_update) = match inst.op.view() {
OperationRef::StandardGate(StandardGate::T) => {
(Param::Float(PI / 4.), PI / 8.)
(Param::Float(FRAC_PI_4), Param::Float(FRAC_PI_8))
}
OperationRef::StandardGate(StandardGate::Tdg) => {
(Param::Float(-PI / 4.0), -PI / 8.)
(Param::Float(-FRAC_PI_4), Param::Float(-FRAC_PI_8))
}
OperationRef::StandardGate(StandardGate::RZ) => {
let param = &inst.params_view()[0];
(param.clone(), 0.)
(param.clone(), Param::Float(0.))
}
OperationRef::StandardGate(StandardGate::Phase)
| OperationRef::StandardGate(StandardGate::U1) => {
let param = &inst.params_view()[0];
(param.clone(), multiply_param(param, 0.5))
}
_ => {
unreachable!("We cannot have gates other than T/Tdg/RZ at this point.");
unreachable!(
"We cannot have gates other than T/Tdg/RZ/P/U1 at this point."
);
}
};
global_phase_update += phase_update;
global_phase_update = radd_param(global_phase_update, phase_update);

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

new_dag.add_global_phase(&Param::Float(global_phase_update))?;
new_dag.add_global_phase(&global_phase_update)?;

// Add Clifford gates to the Qiskit circuit (when required).
// Since we aim to preserve the global phase of the circuit, we add the Clifford operations from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class LitinskiTransformation(TransformationPass):

The list of supported :math:`R_Z`-rotations is:

``["t", "tdg", "rz"]``
``["t", "tdg", "rz", "p", "u1"]``

Example:

Expand Down
5 changes: 5 additions & 0 deletions releasenotes/notes/litinski-phase-gate-197b06e529a53c64.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
features_transpiler:
- The :class:`.LitinskiTransformation` now also supports circuits with
:class:`.PhaseGate` and :class:`.U1Gate` instructions. Previously, the only
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we relate to U1Gate here since it's deprecated?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question - since users could have circuits defined in terms of U1Gate, since it's still part of the official API and supported, I added it here since it doesn't really add any overhead to handle it

accepted parameterized gate was :class:`.RZGate`.
21 changes: 18 additions & 3 deletions test/python/transpiler/test_litinski_transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
PauliEvolutionGate,
PauliProductMeasurement,
PauliProductRotationGate,
U1Gate,
)
from qiskit.circuit.random import random_clifford_circuit
from qiskit.compiler import transpile
Expand Down Expand Up @@ -179,14 +180,15 @@ def test_all_supported_clifford_gates(self):
qc.rz(0.1, 1)
qc.tdg(2)
qc.rz(-0.2, 3)
qc.p(0.3, 0)
qc.append(U1Gate(-0.5), [0])

qc_litinski = LitinskiTransformation()(qc)
ops_litinski = qc_litinski.count_ops()

# make sure the transform was applied
self.assertNotIn("t", ops_litinski)
self.assertNotIn("tdg", ops_litinski)
self.assertNotIn("rz", ops_litinski)
for z_rot in ["t", "tdg", "rz", "p", "u1"]:
self.assertNotIn(z_rot, ops_litinski)

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

self.assertEqual(qct, expected)

def test_p(self):
"""Test the phase gate, ensuring we got the global phase right."""
Comment thread
ShellyGarion marked this conversation as resolved.
angle = 0.231
qc = QuantumCircuit(1)
qc.p(angle, 0)

qct = LitinskiTransformation(use_ppr=True)(qc)
self.assertTrue(np.allclose(Operator(qc).data, Operator(qct).data))

expected = QuantumCircuit(1, global_phase=angle / 2)
expected.append(PauliProductRotationGate(Pauli("Z"), angle), [0])
self.assertEqual(qct, expected)

def test_h_t(self):
"""Test the transform on a circuit with an H-gate and a T-gate."""
qc = QuantumCircuit(2)
Expand Down