Skip to content

Commit fb164bb

Browse files
authored
Adds cirq.num_cnots_required and cirq.to_special (#2892)
Adds cirq.num_two_qubit_gates_required and cirq.to_special. - `cirq.num_cnots_required`: Based on simple linear algebra users can calculate the minimum required two-qubit gates (CZ, CNOT) to implement a two-qubit unitary. - `cirq.to_special`: converts a unitary to a special unitary Context: this is the first PR breaking up #2873. For an overview of the high level design decisions of the whole project see: https://drive.google.com/open?id=1SDEtttIAaTwfV9AUs7XAxeW3AUd0qLoY.
1 parent bba0153 commit fb164bb

File tree

7 files changed

+119
-1
lines changed

7 files changed

+119
-1
lines changed

cirq/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@
144144
match_global_phase,
145145
matrix_commutes,
146146
matrix_from_basis_coefficients,
147+
num_cnots_required,
147148
partial_trace,
148149
partial_trace_of_state_vector_as_mixture,
149150
PAULI_BASIS,
@@ -156,6 +157,7 @@
156157
sub_state_vector,
157158
targeted_conjugate_about,
158159
targeted_left_multiply,
160+
to_special,
159161
unitary_eig,
160162
wavefunction_partial_trace_as_mixture,
161163
)

cirq/linalg/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
KakDecomposition,
3636
kron_factor_4x4_to_2x2s,
3737
map_eigenvalues,
38+
num_cnots_required,
3839
unitary_eig,
3940
scatter_plot_normalized_kak_interaction_coefficients,
4041
so4_to_magic_su2s,
@@ -89,5 +90,6 @@
8990
sub_state_vector,
9091
targeted_conjugate_about,
9192
targeted_left_multiply,
93+
to_special,
9294
wavefunction_partial_trace_as_mixture,
9395
)

cirq/linalg/decompositions.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727
from cirq import value, protocols
2828
from cirq._compat import proper_repr
29-
from cirq.linalg import combinators, diagonalize, predicates
29+
from cirq.linalg import combinators, diagonalize, predicates, transformations
3030

3131
if TYPE_CHECKING:
3232
import cirq
@@ -36,8 +36,16 @@
3636
[0, 1j, 1, 0],
3737
[0, 1j, -1, 0],
3838
[1, 0, 0, -1j]]) * np.sqrt(0.5)
39+
3940
MAGIC_CONJ_T = np.conj(MAGIC.T)
4041

42+
# yapf: disable
43+
YY = np.array([[0, 0, 0, -1],
44+
[0, 0, 1, 0],
45+
[0, 1, 0, 0],
46+
[-1, 0, 0, 0]])
47+
# yapf: enable
48+
4149

4250
def _phase_matrix(angle: float) -> np.ndarray:
4351
return np.diag([1, np.exp(1j * angle)])
@@ -992,3 +1000,48 @@ def _canonicalize_kak_vector(k_vec: np.ndarray, atol: float) -> np.ndarray:
9921000
k_vec[need_diff, 2] *= -1
9931001

9941002
return k_vec
1003+
1004+
1005+
def num_cnots_required(u: np.ndarray, atol: float = 1e-8) -> int:
1006+
"""Returns the min number of CNOT/CZ gates required by a two-qubit unitary.
1007+
1008+
See Proposition III.1, III.2, III.3 in Shende et al. “Recognizing Small-
1009+
Circuit Structure in Two-Qubit Operators and Timing Hamiltonians to Compute
1010+
Controlled-Not Gates”. https://arxiv.org/abs/quant-ph/0308045
1011+
1012+
Args:
1013+
u: a two-qubit unitary
1014+
Returns:
1015+
the number of CNOT or CZ gates required to implement the unitary
1016+
"""
1017+
if u.shape != (4, 4):
1018+
raise ValueError(f"Expected unitary of shape (4,4), instead "
1019+
f"got {u.shape}")
1020+
g = _gamma(transformations.to_special(u))
1021+
# see Fadeev-LeVerrier formula
1022+
a3 = -np.trace(g)
1023+
# no need to check a2 = 6, as a3 = +-4 only happens if the eigenvalues are
1024+
# either all +1 or -1, which unambiguously implies that a2 = 6
1025+
if np.abs(a3 - 4) < atol or np.abs(a3 + 4) < atol:
1026+
return 0
1027+
# see Fadeev-LeVerrier formula
1028+
a2 = (a3 * a3 - np.trace(g @ g)) / 2
1029+
if np.abs(a3) < atol and np.abs(a2 - 2) < atol:
1030+
return 1
1031+
if np.abs(a3.imag) < atol:
1032+
return 2
1033+
return 3
1034+
1035+
1036+
def _gamma(u: np.ndarray) -> np.ndarray:
1037+
"""Gamma function to convert u to the magic basis.
1038+
1039+
See Definition IV.1 in Shende et al. "Minimal Universal Two-Qubit CNOT-based
1040+
Circuits." https://arxiv.org/abs/quant-ph/0308033
1041+
1042+
Args:
1043+
u: a member of SU(4)
1044+
Returns:
1045+
u @ yy @ u.T @ yy, where yy = Y ⊗ Y
1046+
"""
1047+
return u @ YY @ u.T @ YY

cirq/linalg/decompositions_test.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,3 +741,38 @@ def test_kak_decompose(unitary: np.ndarray):
741741
np.testing.assert_allclose(cirq.unitary(circuit), unitary, atol=1e-8)
742742
assert len(circuit) == 5
743743
assert len(list(circuit.all_operations())) == 8
744+
745+
746+
def test_num_two_qubit_gates_required():
747+
for i in range(4):
748+
assert cirq.num_cnots_required(
749+
_two_qubit_circuit_with_cnots(i).unitary()) == i
750+
751+
assert cirq.num_cnots_required(np.eye(4)) == 0
752+
753+
754+
def test_num_two_qubit_gates_required_invalid():
755+
with pytest.raises(ValueError, match="(4,4)"):
756+
cirq.num_cnots_required(np.array([[1]]))
757+
758+
759+
def _two_qubit_circuit_with_cnots(num_cnots=3, a=None, b=None):
760+
random.seed(32123)
761+
if a is None or b is None:
762+
a, b = cirq.LineQubit.range(2)
763+
764+
def random_one_qubit_gate():
765+
return cirq.PhasedXPowGate(phase_exponent=random.random(),
766+
exponent=random.random())
767+
768+
def one_cz():
769+
return [
770+
cirq.CZ.on(a, b),
771+
random_one_qubit_gate().on(a),
772+
random_one_qubit_gate().on(b),
773+
]
774+
775+
return cirq.Circuit([
776+
random_one_qubit_gate().on(a),
777+
random_one_qubit_gate().on(b), [one_cz() for _ in range(num_cnots)]
778+
])

cirq/linalg/transformations.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,3 +504,20 @@ def sub_state_vector(state_vector: np.ndarray,
504504
@deprecated(deadline='v0.10.0', fix='Use `cirq.sub_state_vector` instead.')
505505
def subwavefunction(*args, **kwargs):
506506
return sub_state_vector(*args, **kwargs)
507+
508+
509+
def to_special(u: np.ndarray) -> np.ndarray:
510+
"""Converts a unitary matrix to a special unitary matrix.
511+
512+
All unitary matrices u have |det(u)| = 1.
513+
Also for all d dimensional unitary matrix u, and scalar s:
514+
det(u * s) = det(u) * s^(d)
515+
To find a special unitary matrix from u:
516+
u * det(u)^{-1/d}
517+
518+
Args:
519+
u: the unitary matrix
520+
Returns:
521+
the special unitary matrix
522+
"""
523+
return u * (np.linalg.det(u)**(-1 / len(u)))

cirq/linalg/transformations_test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,3 +671,10 @@ def test_deprecated():
671671
# pylint: disable=unexpected-keyword-arg,no-value-for-parameter
672672
_ = cirq.partial_trace_of_state_vector_as_mixture(wavefunction=a,
673673
keep_indices=[0])
674+
675+
676+
def test_to_special():
677+
u = cirq.testing.random_unitary(4)
678+
su = cirq.to_special(u)
679+
assert not cirq.is_special_unitary(u)
680+
assert cirq.is_special_unitary(su)

rtd_docs/api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,7 @@ Algebra and Representation
671671
cirq.match_global_phase
672672
cirq.matrix_commutes
673673
cirq.matrix_from_basis_coefficients
674+
cirq.num_cnots_required
674675
cirq.partial_trace
675676
cirq.partial_trace_of_state_vector_as_mixture
676677
cirq.reflection_matrix_pow
@@ -679,6 +680,7 @@ Algebra and Representation
679680
cirq.sub_state_vector
680681
cirq.targeted_conjugate_about
681682
cirq.targeted_left_multiply
683+
cirq.to_special
682684
cirq.unitary_eig
683685
cirq.AxisAngleDecomposition
684686
cirq.Duration

0 commit comments

Comments
 (0)