Skip to content

Commit 2b8b407

Browse files
tanujkhattarrht
authored andcommitted
Add CliffordTargetGateset and deprecate ConvertToSingleQubitCliffordGates and ConvertToPauliStringPhasor (quantumlib#5650)
Part of quantumlib#5028
1 parent 7463123 commit 2b8b407

12 files changed

+311
-514
lines changed

cirq-core/cirq/contrib/paulistring/__init__.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,7 @@
1414

1515
"""Methods related to optimizing and transforming PauliStrings."""
1616

17-
from cirq.contrib.paulistring.convert_to_pauli_string_phasors import ConvertToPauliStringPhasors
18-
19-
from cirq.contrib.paulistring.convert_to_clifford_gates import ConvertToSingleQubitCliffordGates
20-
21-
from cirq.contrib.paulistring.convert_gate_set import converted_gate_set
17+
from cirq.contrib.paulistring.clifford_target_gateset import CliffordTargetGateset
2218

2319
from cirq.contrib.paulistring.separate import (
2420
convert_and_separate_circuit,

cirq-core/cirq/contrib/paulistring/clifford_optimize.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,16 @@
1414

1515
from typing import Tuple, cast
1616

17-
from cirq import circuits, ops, protocols
18-
from cirq.contrib.paulistring.convert_gate_set import converted_gate_set
17+
from cirq import circuits, ops, protocols, transformers
18+
from cirq.contrib.paulistring.clifford_target_gateset import CliffordTargetGateset
1919

2020

2121
def clifford_optimized_circuit(circuit: circuits.Circuit, atol: float = 1e-8) -> circuits.Circuit:
2222
# Convert to a circuit with SingleQubitCliffordGates,
2323
# CZs and other ignored gates
24-
c_cliff = converted_gate_set(circuit, no_clifford_gates=False, atol=atol)
24+
c_cliff = transformers.optimize_for_target_gateset(
25+
circuit, gateset=CliffordTargetGateset(atol=atol)
26+
)
2527

2628
all_ops = list(c_cliff.all_operations())
2729

cirq-core/cirq/contrib/paulistring/clifford_optimize_test.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
import cirq
1717

18-
from cirq.contrib.paulistring import converted_gate_set, clifford_optimized_circuit
18+
from cirq.contrib.paulistring import clifford_optimized_circuit, CliffordTargetGateset
1919

2020

2121
def test_optimize():
@@ -28,8 +28,10 @@ def test_optimize():
2828
cirq.CZ(q0, q1),
2929
cirq.X(q1) ** -0.5,
3030
)
31-
c_expected = converted_gate_set(
32-
cirq.Circuit(cirq.CZ(q0, q1), cirq.Z(q0) ** 0.25, cirq.X(q1) ** 0.25, cirq.CZ(q0, q1))
31+
c_expected = cirq.optimize_for_target_gateset(
32+
cirq.Circuit(cirq.CZ(q0, q1), cirq.Z(q0) ** 0.25, cirq.X(q1) ** 0.25, cirq.CZ(q0, q1)),
33+
gateset=CliffordTargetGateset(),
34+
ignore_failures=True,
3335
)
3436

3537
c_opt = clifford_optimized_circuit(c_orig)
@@ -51,7 +53,9 @@ def test_optimize():
5153
def test_remove_czs():
5254
q0, q1 = cirq.LineQubit.range(2)
5355
c_orig = cirq.Circuit(cirq.CZ(q0, q1), cirq.Z(q0) ** 0.5, cirq.CZ(q0, q1))
54-
c_expected = converted_gate_set(cirq.Circuit(cirq.Z(q0) ** 0.5))
56+
c_expected = cirq.optimize_for_target_gateset(
57+
cirq.Circuit(cirq.Z(q0) ** 0.5), gateset=CliffordTargetGateset(), ignore_failures=True
58+
)
5559

5660
c_opt = clifford_optimized_circuit(c_orig)
5761

@@ -72,7 +76,9 @@ def test_remove_czs():
7276
def test_remove_staggered_czs():
7377
q0, q1, q2 = cirq.LineQubit.range(3)
7478
c_orig = cirq.Circuit(cirq.CZ(q0, q1), cirq.CZ(q1, q2), cirq.CZ(q0, q1))
75-
c_expected = converted_gate_set(cirq.Circuit(cirq.CZ(q1, q2)))
79+
c_expected = cirq.optimize_for_target_gateset(
80+
cirq.Circuit(cirq.CZ(q1, q2)), gateset=CliffordTargetGateset(), ignore_failures=True
81+
)
7682

7783
c_opt = clifford_optimized_circuit(c_orig)
7884

@@ -95,10 +101,11 @@ def test_remove_staggered_czs():
95101
def test_with_measurements():
96102
q0, q1 = cirq.LineQubit.range(2)
97103
c_orig = cirq.Circuit(cirq.X(q0), cirq.CZ(q0, q1), cirq.measure(q0, q1, key='m'))
98-
c_expected = converted_gate_set(
99-
cirq.Circuit(cirq.CZ(q0, q1), cirq.X(q0), cirq.Z(q1), cirq.measure(q0, q1, key='m'))
104+
c_expected = cirq.optimize_for_target_gateset(
105+
cirq.Circuit(cirq.CZ(q0, q1), cirq.X(q0), cirq.Z(q1), cirq.measure(q0, q1, key='m')),
106+
gateset=CliffordTargetGateset(),
107+
ignore_failures=True,
100108
)
101-
102109
c_opt = clifford_optimized_circuit(c_orig)
103110

104111
cirq.testing.assert_allclose_up_to_global_phase(c_orig.unitary(), c_opt.unitary(), atol=1e-7)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# Copyright 2022 The Cirq Developers
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from typing import List, Union, Type, cast, TYPE_CHECKING
16+
from enum import Enum
17+
import numpy as np
18+
19+
from cirq import ops, transformers, protocols, linalg
20+
from cirq.type_workarounds import NotImplementedType
21+
22+
if TYPE_CHECKING:
23+
import cirq
24+
25+
26+
def _matrix_to_clifford_op(
27+
mat: np.ndarray, qubit: 'cirq.Qid', *, atol: float
28+
) -> Union[ops.Operation, NotImplementedType]:
29+
rotations = transformers.single_qubit_matrix_to_pauli_rotations(mat, atol)
30+
clifford_gate = ops.SingleQubitCliffordGate.I
31+
for pauli, half_turns in rotations:
32+
if linalg.all_near_zero_mod(half_turns, 0.5):
33+
quarter_turns = round(half_turns * 2) % 4
34+
# quarter_turns will always be 1-sqrt(pauli) / 2-pauli / 3-sqrt(pauli) ** -1.
35+
clifford_gate = clifford_gate.merged_with(
36+
ops.SingleQubitCliffordGate.from_pauli(pauli, sqrt=bool(quarter_turns % 2))
37+
** (1 - 2 * int(quarter_turns == 3))
38+
)
39+
else:
40+
return NotImplemented
41+
return clifford_gate(qubit)
42+
43+
44+
def _matrix_to_pauli_string_phasors(
45+
mat: np.ndarray, qubit: 'cirq.Qid', *, keep_clifford: bool, atol: float
46+
) -> ops.OP_TREE:
47+
rotations = transformers.single_qubit_matrix_to_pauli_rotations(mat, atol)
48+
out_ops: List[ops.GateOperation] = []
49+
for pauli, half_turns in rotations:
50+
if keep_clifford and linalg.all_near_zero_mod(half_turns, 0.5):
51+
cliff_gate = ops.SingleQubitCliffordGate.from_quarter_turns(
52+
pauli, round(half_turns * 2)
53+
)
54+
if out_ops and not isinstance(out_ops[-1], ops.PauliStringPhasor):
55+
gate = cast(ops.SingleQubitCliffordGate, out_ops[-1].gate)
56+
out_ops[-1] = gate.merged_with(cliff_gate)(qubit)
57+
else:
58+
out_ops.append(cliff_gate(qubit))
59+
else:
60+
out_ops.append(
61+
ops.PauliStringPhasor(
62+
ops.PauliString(pauli.on(qubit)), exponent_neg=round(half_turns, 10)
63+
)
64+
)
65+
return out_ops
66+
67+
68+
class CliffordTargetGateset(transformers.TwoQubitCompilationTargetGateset):
69+
"""Target gateset containing CZ + Meas + SingleQubitClifford / PauliStringPhasor gates."""
70+
71+
class SingleQubitTarget(Enum):
72+
SINGLE_QUBIT_CLIFFORDS = 1
73+
PAULI_STRING_PHASORS_AND_CLIFFORDS = 2
74+
PAULI_STRING_PHASORS = 3
75+
76+
def __init__(
77+
self,
78+
*,
79+
single_qubit_target: SingleQubitTarget = SingleQubitTarget.PAULI_STRING_PHASORS_AND_CLIFFORDS, # pylint: disable=line-too-long
80+
atol: float = 1e-8,
81+
):
82+
"""Initializes CliffordTargetGateset
83+
84+
Args:
85+
single_qubit_target: Specifies the decomposition strategy for single qubit gates.
86+
SINGLE_QUBIT_CLIFFORDS: Decompose all single qubit gates to
87+
`cirq.SingleQubitCliffordGate`.
88+
PAULI_STRING_PHASORS_AND_CLIFFORDS: Accept both `cirq.SingleQubitCliffordGate` and
89+
`cirq.PauliStringPhasorGate`; but decompose unknown gates into
90+
`cirq.PauliStringPhasorGate`.
91+
PAULI_STRING_PHASORS: Decompose all single qubit gates to
92+
`cirq.PauliStringPhasorGate`.
93+
atol: A limit on the amount of absolute error introduced by the decomposition.
94+
"""
95+
self.atol = atol
96+
self.single_qubit_target = single_qubit_target
97+
gates: List[Union['cirq.Gate', Type['cirq.Gate']]] = [ops.CZ, ops.MeasurementGate]
98+
if single_qubit_target in [
99+
self.SingleQubitTarget.SINGLE_QUBIT_CLIFFORDS,
100+
self.SingleQubitTarget.PAULI_STRING_PHASORS_AND_CLIFFORDS,
101+
]:
102+
gates.append(ops.SingleQubitCliffordGate)
103+
if single_qubit_target in [
104+
self.SingleQubitTarget.PAULI_STRING_PHASORS_AND_CLIFFORDS,
105+
self.SingleQubitTarget.PAULI_STRING_PHASORS,
106+
]:
107+
gates.append(ops.PauliStringPhasorGate)
108+
super().__init__(*gates)
109+
110+
def _decompose_single_qubit_operation(
111+
self, op: 'cirq.Operation', _
112+
) -> Union[NotImplementedType, 'cirq.OP_TREE']:
113+
if not protocols.has_unitary(op):
114+
return NotImplemented
115+
mat = protocols.unitary(op)
116+
keep_clifford = (
117+
self.single_qubit_target == self.SingleQubitTarget.PAULI_STRING_PHASORS_AND_CLIFFORDS
118+
)
119+
return (
120+
_matrix_to_clifford_op(mat, op.qubits[0], atol=self.atol)
121+
if self.single_qubit_target == self.SingleQubitTarget.SINGLE_QUBIT_CLIFFORDS
122+
else _matrix_to_pauli_string_phasors(
123+
mat, op.qubits[0], keep_clifford=keep_clifford, atol=self.atol
124+
)
125+
)
126+
127+
def _decompose_two_qubit_operation(
128+
self, op: 'cirq.Operation', _
129+
) -> Union[NotImplementedType, 'cirq.OP_TREE']:
130+
if not protocols.has_unitary(op):
131+
return NotImplemented
132+
return transformers.two_qubit_matrix_to_cz_operations(
133+
op.qubits[0],
134+
op.qubits[1],
135+
protocols.unitary(op),
136+
allow_partial_czs=False,
137+
atol=self.atol,
138+
)
139+
140+
@property
141+
def postprocess_transformers(self) -> List['cirq.TRANSFORMER']:
142+
"""List of transformers which should be run after decomposing individual operations."""
143+
144+
def rewriter(o: 'cirq.CircuitOperation'):
145+
result = self._decompose_single_qubit_operation(o, -1)
146+
return o.circuit.all_operations() if result is NotImplemented else result
147+
148+
return [
149+
transformers.create_transformer_with_kwargs(
150+
transformers.merge_k_qubit_unitaries, k=1, rewriter=rewriter
151+
),
152+
transformers.drop_negligible_operations,
153+
transformers.drop_empty_moments,
154+
]

0 commit comments

Comments
 (0)