Repository: quantumlib/Cirq

Issue:
Property missing for instances of PhasedXPowGate
Steps to reproduce:

```
In [2]: g1 = cirq.PhasedXPowGate(exponent=0.2, phase_exponent=0.4)                                                                                                                      

In [3]: g2 = cirq.PhasedXPowGate(exponent=0.2, phase_exponent=0.5)                                                                                                                      

In [4]: g1.phase_exponent                                                                                                                                                               
Out[4]: 0.4

In [5]: g2.phase_exponent                                                                                                                                                               
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-0b9226f7e654> in <module>
----> 1 g2.phase_exponent

AttributeError: 'YPowGate' object has no attribute 'phase_exponent'
```

The cause is the fact that `__new__()` intercepts and special-cases gate instantiations when phase_exponent is -1/2, 0, 1/2 or 1. It then returns an `XPowGate` or `YPowGate` which don't have `phase_exponent` property.


Discussion:

Resolving patch:
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
@@ -111,6 +111,10 @@ def _qasm_(self,
             return args.format('rx({0:half_turns}) {1};\n',
                                self._exponent, qubits[0])
 
+    @property
+    def phase_exponent(self):
+        return 0.0
+
     def _phase_by_(self, phase_turns, qubit_index):
         """See `cirq.SupportsPhase`."""
         return cirq.ops.phased_x_gate.PhasedXPowGate(
@@ -187,6 +191,10 @@ def _qasm_(self,
             return args.format('ry({0:half_turns}) {1};\n',
                                self._exponent, qubits[0])
 
+    @property
+    def phase_exponent(self):
+        return 0.5
+
     def _phase_by_(self, phase_turns, qubit_index):
         """See `cirq.SupportsPhase`."""
         return cirq.ops.phased_x_gate.PhasedXPowGate(


Test patch:
diff --git a/cirq/ops/phased_x_gate_test.py b/cirq/ops/phased_x_gate_test.py
--- a/cirq/ops/phased_x_gate_test.py
+++ b/cirq/ops/phased_x_gate_test.py
@@ -12,6 +12,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import itertools
+
+import numpy as np
 import pytest
 
 import cirq
@@ -195,3 +198,22 @@ def test_phase_by():
     g = cirq.PhasedXPowGate(phase_exponent=0.5)
     g2 = cirq.phase_by(g, 0.125, 0)
     assert g2 == cirq.PhasedXPowGate(phase_exponent=0.75)
+
+
+@pytest.mark.parametrize('exponent,phase_exponent',
+        itertools.product(np.arange(-2.5, 2.75, 0.25), repeat=2),
+)
+def test_exponent_consistency(exponent, phase_exponent):
+    """Verifies that instances of PhasedX gate expose consistent exponents."""
+    g = cirq.PhasedXPowGate(exponent=exponent, phase_exponent=phase_exponent)
+    assert g.exponent in [exponent, -exponent]
+    assert g.phase_exponent in [
+            cirq.value.canonicalize_half_turns(g.phase_exponent)]
+
+    g2 = cirq.PhasedXPowGate(exponent=g.exponent,
+                             phase_exponent=g.phase_exponent)
+    assert g == g2
+
+    u = cirq.protocols.unitary(g)
+    u2 = cirq.protocols.unitary(g2)
+    assert np.all(u == u2)