Skip to content

Commit cc6a172

Browse files
committed
Add method to get a Clifford gate and phase global phase from unitary
Fixes #2847
1 parent eac80d1 commit cc6a172

File tree

3 files changed

+39
-12
lines changed

3 files changed

+39
-12
lines changed

cirq-core/cirq/ops/clifford_gate.py

+21
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,27 @@ def from_unitary(u: np.ndarray) -> Optional['SingleQubitCliffordGate']:
620620
_to_clifford_tableau(x_to=x_to, z_to=z_to)
621621
)
622622

623+
@classmethod
624+
def from_unitary_with_global_phase(cls, u: np.ndarray) -> Optional[Tuple['SingleQubitCliffordGate', complex]]:
625+
"""Creates Clifford gate with given unitary, including global phase.
626+
627+
Args:
628+
u: 2x2 unitary matrix of a Clifford gate.
629+
630+
Returns:
631+
A tuple of a SingleQubitCliffordGate and a global phase, such that
632+
the gate unitary (as given by `cirq.unitary`) times the global phase
633+
is identical to the given unitary `u`; or `None` if `u` is not the
634+
matrix of a single-qubit Clifford gate.
635+
"""
636+
gate = cls.from_unitary(u)
637+
if gate is None:
638+
return None
639+
# Find the entry with the largest magnitude in the input unitary, to find
640+
# the global phase difference between the input unitary and the gate unitary.
641+
k = max(np.ndindex(*u.shape), key=lambda t: abs(u[t]))
642+
return gate, u[k] / protocols.unitary(gate)[k]
643+
623644
def pauli_tuple(self, pauli: Pauli) -> Tuple[Pauli, bool]:
624645
"""Returns a tuple of a Pauli operator and a boolean.
625646

cirq-core/cirq/ops/clifford_gate_test.py

+12
Original file line numberDiff line numberDiff line change
@@ -530,26 +530,38 @@ def test_from_unitary(clifford_gate):
530530
result_gate = cirq.SingleQubitCliffordGate.from_unitary(u)
531531
assert result_gate == clifford_gate
532532

533+
result_gate2, global_phase = cirq.SingleQubitCliffordGate.from_unitary_with_global_phase(u)
534+
assert result_gate2 == result_gate
535+
assert np.allclose(cirq.unitary(result_gate2) * global_phase, u)
536+
533537

534538
def test_from_unitary_with_phase_shift():
535539
u = np.exp(0.42j) * cirq.unitary(cirq.SingleQubitCliffordGate.Y_sqrt)
536540
gate = cirq.SingleQubitCliffordGate.from_unitary(u)
537541

538542
assert gate == cirq.SingleQubitCliffordGate.Y_sqrt
539543

544+
gate2, global_phase = cirq.SingleQubitCliffordGate.from_unitary_with_global_phase(u)
545+
assert gate2 == gate
546+
assert np.allclose(cirq.unitary(gate2) * global_phase, u)
547+
548+
540549

541550
def test_from_unitary_not_clifford():
542551
# Not a single-qubit gate.
543552
u = cirq.unitary(cirq.CNOT)
544553
assert cirq.SingleQubitCliffordGate.from_unitary(u) is None
554+
assert cirq.SingleQubitCliffordGate.from_unitary_with_global_phase(u) is None
545555

546556
# Not an unitary matrix.
547557
u = 2 * cirq.unitary(cirq.X)
548558
assert cirq.SingleQubitCliffordGate.from_unitary(u) is None
559+
assert cirq.SingleQubitCliffordGate.from_unitary_with_global_phase(u) is None
549560

550561
# Not a Clifford gate.
551562
u = cirq.unitary(cirq.T)
552563
assert cirq.SingleQubitCliffordGate.from_unitary(u) is None
564+
assert cirq.SingleQubitCliffordGate.from_unitary_with_global_phase(u) is None
553565

554566

555567
@pytest.mark.parametrize('trans_x,trans_z', _all_rotation_pairs())

cirq-core/cirq/sim/clifford/stabilizer_simulation_state.py

+6-12
Original file line numberDiff line numberDiff line change
@@ -161,21 +161,15 @@ def _strat_act_from_single_qubit_decompose(
161161
if not has_unitary(val):
162162
return NotImplemented
163163
u = unitary(val)
164-
clifford_gate = SingleQubitCliffordGate.from_unitary(u)
165-
if clifford_gate is not None:
166-
# Gather the effective unitary applied so as to correct for the
167-
# global phase later.
168-
final_unitary = np.eye(2)
164+
gate_and_phase = SingleQubitCliffordGate.from_unitary_with_global_phase(u)
165+
if gate_and_phase is not None:
166+
clifford_gate, global_phase = gate_and_phase
167+
# Decompose gate into rotations and apply them.
169168
for axis, quarter_turns in clifford_gate.decompose_rotation():
170169
gate = axis ** (quarter_turns / 2)
171170
self._strat_apply_gate(gate, qubits)
172-
final_unitary = np.matmul(unitary(gate), final_unitary)
173-
174-
# Find the entry with the largest magnitude in the input unitary.
175-
k = max(np.ndindex(*u.shape), key=lambda t: abs(u[t]))
176-
# Correct the global phase that wasn't conserved in the above
177-
# decomposition.
178-
self._state.apply_global_phase(u[k] / final_unitary[k])
171+
# Apply global phase.
172+
self._state.apply_global_phase(global_phase)
179173
return True
180174

181175
return NotImplemented

0 commit comments

Comments
 (0)