Repository: quantumlib/Cirq

Issue:
Implement cirq.H with EigenGate so cirq.H**0.5 works
writing `cirq.H(ancilla_qubit) ** 0.5` gives the error:
"Expected a <class 'cirq.ops.gate_features.ExtrapolatableEffect'> but got cirq.H"

This is probably expected behavior but I don't understand it. Why isn't Hadamard extrapolatable?


Discussion:
It's just not implemented. I agree that it should be.

I'm labelling this "good first issue" because all that really needs to be done is to mimic RotXGate but with the Hadamard gate's eigendecomposition.
How do you calculate `_eigen_components`? The docs give an example with Pauli-X, but i'm having a problem deriving them. From the docs: `The first component of a tuple is how much that eigenspace should be phased by applying the gate` How is that phase factor calculated?
The eigenvalues of a unitary are of the form λ=e<sup>iθ</sup>; the phase factor is θ/π.

The second component is the projector onto the subspace with eigenvalue λ.
Alright. I still don't know where `[[0.5, 0.5], [0.5, 0.5]]` comes from. The eigenvalues of Pauli-X are `λ_1 = -1` and `λ_2 = 1` so for `-1 = e^iθ` , `θ = π`, so the phase factor would be `1`, for `1 = e^iθ`, `θ = 0`. Phase factor is 0. Where does `[[0.5, 0.5], [0.5, 0.5]]` fit in with this?
The ±1 eigenvalue corresponds to the eigenvector |±⟩ = [[1 / sqrt(2)], [±1 / sqrt(2)]]. The projector is then 
|±⟩⟨±| = [[1/2, ±1/2], [±1/2, 1/2]].

Resolving patch:
diff --git a/cirq/contrib/paulistring/convert_to_pauli_string_phasors.py b/cirq/contrib/paulistring/convert_to_pauli_string_phasors.py
--- a/cirq/contrib/paulistring/convert_to_pauli_string_phasors.py
+++ b/cirq/contrib/paulistring/convert_to_pauli_string_phasors.py
@@ -66,13 +66,13 @@ def _matrix_to_pauli_string_phasors(self,
                                         mat: np.ndarray,
                                         qubit: ops.QubitId) -> ops.OP_TREE:
         rotations = decompositions.single_qubit_matrix_to_pauli_rotations(
-                                       mat, self.tolerance)
+            mat, self.tolerance)
         out_ops = []  # type: List[ops.Operation]
         for pauli, half_turns in rotations:
             if (self.keep_clifford
-                and self._tol.all_near_zero_mod(half_turns, 0.5)):
+                    and self._tol.all_near_zero_mod(half_turns, 0.5)):
                 cliff_gate = ops.CliffordGate.from_quarter_turns(
-                                    pauli, round(half_turns * 2))
+                    pauli, round(half_turns * 2))
                 if out_ops and not isinstance(out_ops[-1], PauliStringPhasor):
                     op = cast(ops.GateOperation, out_ops[-1])
                     gate = cast(ops.CliffordGate, op.gate)
@@ -83,7 +83,8 @@ def _matrix_to_pauli_string_phasors(self,
             else:
                 pauli_string = ops.PauliString.from_single(qubit, pauli)
                 out_ops.append(
-                    PauliStringPhasor(pauli_string, half_turns=half_turns))
+                    PauliStringPhasor(pauli_string,
+                                      half_turns=round(half_turns, 10)))
         return out_ops
 
     def _convert_one(self, op: ops.Operation) -> ops.OP_TREE:
@@ -93,14 +94,14 @@ def _convert_one(self, op: ops.Operation) -> ops.OP_TREE:
 
         if (self.keep_clifford
             and isinstance(op, ops.GateOperation)
-            and isinstance(op.gate, ops.CliffordGate)):
+                and isinstance(op.gate, ops.CliffordGate)):
             return op
 
         # Single qubit gate with known matrix?
         mat = self.extensions.try_cast(ops.KnownMatrix, op)
         if mat is not None and len(op.qubits) == 1:
             return self._matrix_to_pauli_string_phasors(
-                            mat.matrix(), op.qubits[0])
+                mat.matrix(), op.qubits[0])
 
         # Just let it be?
         if self.ignore_failures:
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
@@ -13,7 +13,6 @@
 # limitations under the License.
 
 """Quantum gates that are commonly used in the literature."""
-import math
 from typing import Union, Tuple, Optional, List, Callable, cast, Iterable
 
 import numpy as np
@@ -349,8 +348,8 @@ class MeasurementGate(raw_types.Gate,
         key: The string key of the measurement.
         invert_mask: A list of values indicating whether the corresponding
             qubits should be flipped. The list's length must not be longer than
-            the number of qubits, but it is permitted to be shorted. Qubits with
-            indices past the end of the mask are not flipped.
+            the number of qubits, but it is permitted to be shorted.
+    Qubits with indices past the end of the mask are not flipped.
     """
 
     def __init__(self,
@@ -414,7 +413,7 @@ def known_qasm_output(self,
         for i, (qubit, inv) in enumerate(zip(qubits, invert_mask)):
             if inv:
                 lines.append(args.format(
-                        'x {0};  // Invert the following measurement\n', qubit))
+                    'x {0};  // Invert the following measurement\n', qubit))
             lines.append(args.format('measure {0} -> {1:meas}[{2}];\n',
                                      qubit, self.key, i))
         return ''.join(lines)
@@ -490,36 +489,86 @@ def measure_each(*qubits: raw_types.QubitId,
 T = Z**0.25
 
 
-class HGate(gate_features.CompositeGate,
+class HGate(eigen_gate.EigenGate,
             gate_features.TextDiagrammable,
-            gate_features.ReversibleEffect,
-            gate_features.KnownMatrix,
+            gate_features.CompositeGate,
             gate_features.SingleQubitGate,
             gate_features.QasmConvertibleGate):
     """180 degree rotation around the X+Z axis of the Bloch sphere."""
 
-    def text_diagram_info(self, args: gate_features.TextDiagramInfoArgs
-                          ) -> gate_features.TextDiagramInfo:
-        return gate_features.TextDiagramInfo(('H',))
+    def __init__(self, *,  # Forces keyword args.
+                 half_turns: Optional[Union[value.Symbol, float]] = None,
+                 rads: Optional[float] = None,
+                 degs: Optional[float] = None) -> None:
+        """Initializes the gate.
+
+        At most one angle argument may be specified. If more are specified,
+        the result is considered ambiguous and an error is thrown. If no angle
+        argument is given, the default value of one half turn is used.
+
+        Args:
+            half_turns: The relative phasing of H's eigenstates, in half_turns.
+            rads: The relative phasing of H's eigenstates, in radians.
+            degs: The relative phasing of H's eigenstates, in degrees.
+        """
+        super().__init__(exponent=value.chosen_angle_to_half_turns(
+            half_turns=half_turns,
+            rads=rads,
+            degs=degs))
+
+    def _canonical_exponent_period(self) -> Optional[float]:
+        return 2
+
+    def _with_exponent(self,
+                       exponent: Union[value.Symbol, float]) -> 'HGate':
+        return HGate(half_turns=exponent)
+
+    def _eigen_components(self):
+        component0 = (np.array([[(3 + 2 * np.sqrt(2)),
+                                 (1 + np.sqrt(2))],
+                                [(1 + np.sqrt(2)),
+                                 (1)]])) / (2 * (2 + np.sqrt(2)))
+
+        component1 = (np.array([[(3 - 2 * np.sqrt(2)),
+                                 (1 - np.sqrt(2))],
+                                [(1 - np.sqrt(2)),
+                                 (1)]])) / (2 * (2 - np.sqrt(2)))
+
+        return [(0, component0), (1, component1), ]
+
+    @property
+    def half_turns(self) -> Union[value.Symbol, float]:
+        return self._exponent
 
     def default_decompose(self, qubits):
         q = qubits[0]
-        yield Y(q)**0.5
-        yield X(q)
+
+        if self._exponent == 1:
+            yield Y(q)**0.5, X(q)
+            return
+
+        yield Y(q)**0.25
+        yield X(q)**self.half_turns
+        yield Y(q)**-0.25
 
     def inverse(self):
         return self
 
-    def matrix(self):
-        """See base class."""
-        s = math.sqrt(0.5)
-        return np.array([[s, s], [s, -s]])
+    def text_diagram_info(self, args: gate_features.TextDiagramInfoArgs
+                          ) -> gate_features.TextDiagramInfo:
+        return gate_features.TextDiagramInfo(('H',))
 
     def known_qasm_output(self,
                           qubits: Tuple[raw_types.QubitId, ...],
                           args: gate_features.QasmOutputArgs) -> Optional[str]:
         args.validate_version('2.0')
-        return args.format('h {0};\n', qubits[0])
+        if self.half_turns == 1:
+            return args.format('h {0};\n', qubits[0])
+        else:
+            return args.format('ry({0:half_turns}) {3};\n'
+                               'rx({1:half_turns}) {3};\n'
+                               'ry({2:half_turns}) {3};\n',
+                               0.25,  self.half_turns, -0.25, qubits[0])
 
     def __str__(self):
         return 'H'
@@ -773,4 +822,5 @@ def __repr__(self):
         return '(cirq.ISWAP**{!r})'.format(self.exponent)
 
 
-ISWAP = ISwapGate()  # Swaps two qubits while phasing the swapped subspace by i.
+# Swaps two qubits while phasing the swapped subspace by i.
+ISWAP = ISwapGate()


Test patch:
diff --git a/cirq/circuits/qasm_output_test.py b/cirq/circuits/qasm_output_test.py
--- a/cirq/circuits/qasm_output_test.py
+++ b/cirq/circuits/qasm_output_test.py
@@ -46,7 +46,7 @@ def test_empty_circuit():
     q0, = _make_qubits(1)
     output = cirq.QasmOutput((), (q0,))
     assert (str(output) ==
-"""OPENQASM 2.0;
+            """OPENQASM 2.0;
 include "qelib1.inc";
 
 
@@ -57,11 +57,10 @@ def test_empty_circuit():
 
 def test_header():
     q0, = _make_qubits(1)
-    output = cirq.QasmOutput((), (q0,), header=
-"""My test circuit
+    output = cirq.QasmOutput((), (q0,), header="""My test circuit
 Device: Bristlecone""")
     assert (str(output) ==
-"""// My test circuit
+            """// My test circuit
 // Device: Bristlecone
 
 OPENQASM 2.0;
@@ -72,13 +71,12 @@ def test_header():
 qreg q[1];
 """)
 
-    output = cirq.QasmOutput((), (q0,), header=
-"""
+    output = cirq.QasmOutput((), (q0,), header="""
 My test circuit
 Device: Bristlecone
 """)
     assert (str(output) ==
-"""//
+            """//
 // My test circuit
 // Device: Bristlecone
 //
@@ -96,7 +94,7 @@ def test_single_gate_no_parameter():
     q0, = _make_qubits(1)
     output = cirq.QasmOutput((cirq.X(q0),), (q0,))
     assert (str(output) ==
-"""OPENQASM 2.0;
+            """OPENQASM 2.0;
 include "qelib1.inc";
 
 
@@ -112,7 +110,7 @@ def test_single_gate_with_parameter():
     q0, = _make_qubits(1)
     output = cirq.QasmOutput((cirq.X(q0) ** 0.25,), (q0,))
     assert (str(output) ==
-"""OPENQASM 2.0;
+            """OPENQASM 2.0;
 include "qelib1.inc";
 
 
@@ -124,11 +122,29 @@ def test_single_gate_with_parameter():
 """)
 
 
+def test_h_gate_with_parameter():
+    q0, = _make_qubits(1)
+    output = cirq.QasmOutput((cirq.H(q0) ** 0.25,), (q0,))
+    assert (str(output) ==
+            """OPENQASM 2.0;
+include "qelib1.inc";
+
+
+// Qubits: [q0]
+qreg q[1];
+
+
+ry(pi*0.25) q[0];
+rx(pi*0.25) q[0];
+ry(pi*-0.25) q[0];
+""")
+
+
 def test_precision():
     q0, = _make_qubits(1)
     output = cirq.QasmOutput((cirq.X(q0) ** 0.1234567,), (q0,), precision=3)
     assert (str(output) ==
-"""OPENQASM 2.0;
+            """OPENQASM 2.0;
 include "qelib1.inc";
 
 
@@ -155,7 +171,7 @@ def test_save_to_file():
         with open(file_path, 'r') as f:
             file_content = f.read()
     assert (file_content ==
-"""OPENQASM 2.0;
+            """OPENQASM 2.0;
 include "qelib1.inc";
 
 
@@ -193,7 +209,6 @@ class DummyCompositeOperation(cirq.Operation, cirq.CompositeOperation):
         qubits = (q0,)
         with_qubits = NotImplemented
 
-
         def default_decompose(self):
             return cirq.X(self.qubits[0])
 
@@ -287,7 +302,7 @@ def test_output_unitary_same_as_qiskit():
     qiskit_unitary = result.get_unitary()
 
     cirq.testing.assert_allclose_up_to_global_phase(
-                    cirq_unitary, qiskit_unitary, rtol=1e-8, atol=1e-8)
+        cirq_unitary, qiskit_unitary, rtol=1e-8, atol=1e-8)
 
 
 def test_output_format():
@@ -301,7 +316,7 @@ def filter_unpredictable_numbers(text):
                              precision=5)
     assert (filter_unpredictable_numbers(str(output)) ==
             filter_unpredictable_numbers(
-"""// Generated from Cirq
+        """// Generated from Cirq
 
 OPENQASM 2.0;
 include "qelib1.inc";
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
@@ -17,6 +17,7 @@
 
 import cirq
 
+
 H = np.array([[1, 1], [1, -1]]) * np.sqrt(0.5)
 HH = cirq.kron(H, H)
 QFT2 = np.array([[1, 1, 1, 1],
@@ -152,6 +153,24 @@ def test_x_matrix():
                        np.array([[1 - 1j, 1 + 1j], [1 + 1j, 1 - 1j]]) / 2)
 
 
+def test_h_matrix():
+    sqrt = (cirq.H**0.5).matrix()
+    m = np.dot(sqrt, sqrt)
+    assert np.allclose(m, cirq.H.matrix(), atol=1e-8)
+
+
+def test_H_decompose():
+    a = cirq.NamedQubit('a')
+
+    original = cirq.HGate(half_turns=0.5)
+    decomposed = cirq.Circuit.from_ops(original.default_decompose([a]))
+
+    cirq.testing.assert_allclose_up_to_global_phase(
+        original.matrix(),
+        decomposed.to_unitary_matrix(),
+        atol=1e-8)
+
+
 def test_runtime_types_of_rot_gates():
     for gate_type in [cirq.Rot11Gate,
                       cirq.RotXGate,
@@ -253,6 +272,7 @@ def test_cnot_power():
         cirq.Circuit.from_ops(g.default_decompose([a, b])).to_unitary_matrix(),
         atol=1e-8)
 
+
 def test_cnot_keyword_arguments():
     a = cirq.NamedQubit('a')
     b = cirq.NamedQubit('b')
@@ -263,6 +283,7 @@ def test_cnot_keyword_arguments():
     eq_tester.add_equality_group(cirq.CNOT(b, a),
                                  cirq.CNOT(control=b, target=a))
 
+
 def test_cnot_keyword_not_equal():
     a = cirq.NamedQubit('a')
     b = cirq.NamedQubit('b')
@@ -272,6 +293,7 @@ def test_cnot_keyword_not_equal():
         eq_tester.add_equality_group(cirq.CNOT(a, b),
                                      cirq.CNOT(target=a, control=b))
 
+
 def test_cnot_keyword_too_few_arguments():
     a = cirq.NamedQubit('a')
 
@@ -286,6 +308,7 @@ def test_cnot_mixed_keyword_and_positional_arguments():
     with pytest.raises(ValueError):
         _ = cirq.CNOT(a, target=b)
 
+
 def test_cnot_unknown_keyword_argument():
     a = cirq.NamedQubit('a')
     b = cirq.NamedQubit('b')
@@ -293,6 +316,7 @@ def test_cnot_unknown_keyword_argument():
     with pytest.raises(ValueError):
         _ = cirq.CNOT(target=a, controlled=b)
 
+
 def test_cnot_decomposes_despite_symbol():
     a = cirq.NamedQubit('a')
     b = cirq.NamedQubit('b')