Repository: quantumlib/Cirq

Issue:
Add CZRotation?
After the addition of `ISwapRotation` in #2488, would it make sense to also add a `CZRotation` gate which takes the controlled angle as an argument? I usually find myself working mostly with angles, for instance for varitional algorithms, so I currently have to convert to exponents when creating circuits in Cirq. 


Discussion:
I'm worried that the matrix of this gate would be ambiguous to users. What's the matrix at pi/2?

1. A Controlled Rz, which the name reads like, would be `diag(1, 1, -i, i)`.
2. Most other R / rotation gates have the form `e^(i G t)`, implying the matrix `diag(-i, -i, -i, i)`
3. If this is strictly an angle conversion, the matrix would be `diag(1, 1, 1, i)`.
I see what you mean. 3 is what I had in mind, and that is also what `cirq.CZ**0.5` yields. I think the trouble is due to what global phase one uses for the Z gate that is controlled? For instance,
``` python
print(cirq.allclose_up_to_global_phase(cirq.unitary(cirq.Rz(np.pi).controlled(1)), cirq.unitary(cirq.CZ)))
False
print(cirq.allclose_up_to_global_phase(cirq.unitary(cirq.ZPowGate().controlled(1)), cirq.unitary(cirq.CZ)))
True
```
which was a bit confusing to me at first.

Regardless, I think that it is quite common to want a gate with `exp(i*theta/4*(ZZ-ZI-IZ)) = diag(1, 1, 1, e^(i*theta))`. Perhaps a good name and documentation can minimize any ambiguity?


Hi @andbe91 - with the `TwoQubitDiagonalGate` since then there is yet another way to achieve this:

```
np.testing.assert_allclose(np.diag([1,1,1,1j]), cirq.unitary(cirq.TwoQubitDiagonalGate([0,0,0, np.pi/2])))
```

An idea here could be `CPhase` (similar to the quil version) which could be a shortcut to TwoQubitDiagonalGate.
What do we think about that name? 


Also @viathor, not sure if you want to work on this - but you are assigned.
```
CPhase(theta) = cirq.TwoQubitDiagonalGate([0,0,0, theta])
```
definitely seems logical to me. I'll let others decide on naming and if it is something we want.
Discussed on Cirq Cynq: 

``
cirq.cphase(angle) --> cirq.CZPowGate ** f(angle)
``

Resolving patch:
diff --git a/cirq/__init__.py b/cirq/__init__.py
--- a/cirq/__init__.py
+++ b/cirq/__init__.py
@@ -179,6 +179,7 @@
     CNotPowGate,
     ControlledGate,
     ControlledOperation,
+    cphase,
     CSWAP,
     CSwapGate,
     CX,
diff --git a/cirq/ops/__init__.py b/cirq/ops/__init__.py
--- a/cirq/ops/__init__.py
+++ b/cirq/ops/__init__.py
@@ -50,6 +50,7 @@
 from cirq.ops.common_gates import (
     CNOT,
     CNotPowGate,
+    cphase,
     CX,
     CXPowGate,
     CZ,
diff --git a/cirq/ops/common_gates.py b/cirq/ops/common_gates.py
--- a/cirq/ops/common_gates.py
+++ b/cirq/ops/common_gates.py
@@ -1221,6 +1221,12 @@ def rz(rads: value.TParamVal) -> ZPowGate:
     return ZPowGate(exponent=rads / pi, global_shift=-0.5)
 
 
+def cphase(rads: value.TParamVal) -> CZPowGate:
+    """Returns a gate with the matrix diag(1, 1, 1, e^{i rads}."""
+    pi = sympy.pi if protocols.is_parameterized(rads) else np.pi
+    return CZPowGate(exponent=rads / pi)
+
+
 H = HPowGate()
 document(
     H, """The Hadamard gate.


Test patch:
diff --git a/cirq/ops/common_gates_test.py b/cirq/ops/common_gates_test.py
--- a/cirq/ops/common_gates_test.py
+++ b/cirq/ops/common_gates_test.py
@@ -601,20 +601,21 @@ def test_text_diagrams():
                            cirq.rx(sympy.Symbol('x')).on(a), cirq.CZ(a, b),
                            cirq.CNOT(a, b), cirq.CNOT(b, a),
                            cirq.H(a)**0.5, cirq.I(a),
-                           cirq.IdentityGate(2)(a, b))
+                           cirq.IdentityGate(2)(a, b),
+                           cirq.cphase(sympy.pi * sympy.Symbol('t')).on(a, b))
 
     cirq.testing.assert_has_diagram(
         circuit, """
-a: ───X───Y───Z───Z^x───Rx(x)───@───@───X───H^0.5───I───I───
-                                │   │   │               │
-b: ─────────────────────────────@───X───@───────────────I───
+a: ───X───Y───Z───Z^x───Rx(x)───@───@───X───H^0.5───I───I───@─────
+                                │   │   │               │   │
+b: ─────────────────────────────@───X───@───────────────I───@^t───
 """)
 
     cirq.testing.assert_has_diagram(circuit,
                                     """
-a: ---X---Y---Z---Z^x---Rx(x)---@---@---X---H^0.5---I---I---
-                                |   |   |               |
-b: -----------------------------@---X---@---------------I---
+a: ---X---Y---Z---Z^x---Rx(x)---@---@---X---H^0.5---I---I---@-----
+                                |   |   |               |   |
+b: -----------------------------@---X---@---------------I---@^t---
 """,
                                     use_unicode_characters=False)
 
@@ -741,6 +742,10 @@ def test_str():
 
     assert str(cirq.CX) == 'CNOT'
     assert str(cirq.CNOT**0.5) == 'CNOT**0.5'
+    assert str(cirq.CZ) == 'CZ'
+    assert str(cirq.CZ**0.5) == 'CZ**0.5'
+    assert str(cirq.cphase(np.pi)) == 'CZ'
+    assert str(cirq.cphase(np.pi / 2)) == 'CZ**0.5'
 
 
 def test_rx_unitary():
@@ -806,6 +811,21 @@ def test_rz_unitary():
                                np.array([[1j, 0], [0, -1j]]))
 
 
+@pytest.mark.parametrize('angle_rads, expected_unitary', [
+    (0, np.eye(4)),
+    (1, np.diag([1, 1, 1, np.exp(1j)])),
+    (np.pi / 2, np.diag([1, 1, 1, 1j])),
+])
+def test_cphase_unitary(angle_rads, expected_unitary):
+    np.testing.assert_allclose(cirq.unitary(cirq.cphase(angle_rads)),
+                               expected_unitary)
+
+
+def test_parameterized_cphase():
+    assert cirq.cphase(sympy.pi) == cirq.CZ
+    assert cirq.cphase(sympy.pi / 2) == cirq.CZ**0.5
+
+
 def test_x_stabilizer():
     gate = cirq.X
     assert cirq.has_stabilizer_effect(gate)