Repository: quantumlib/Cirq

Issue:
Qasm output should include identity gates



Discussion:

Resolving patch:
diff --git a/cirq/circuits/qasm_output.py b/cirq/circuits/qasm_output.py
--- a/cirq/circuits/qasm_output.py
+++ b/cirq/circuits/qasm_output.py
@@ -14,7 +14,7 @@
 
 """Utility classes for representing QASM."""
 
-from typing import Set  # pylint: disable=unused-import
+from typing import Set, Any  # pylint: disable=unused-import
 from typing import (
     Callable, Dict, Optional, Sequence, Tuple, Union
 )
@@ -25,6 +25,7 @@
 from cirq import ops, linalg, protocols, value
 
 
+@value.value_equality(unhashable=True, approximate=True)
 class QasmUGate(ops.SingleQubitGate):
 
     def __init__(self, theta, phi, lmda) -> None:
@@ -73,11 +74,8 @@ def _unitary_(self) -> np.ndarray:
         ]
         return linalg.dot(*map(protocols.unitary, operations))
 
-    def __eq__(self, other):
-        return isinstance(other, QasmUGate) and \
-               other.lmda == self.lmda and \
-               other.theta == self.theta and \
-               other.phi == self.phi
+    def _value_equality_values_(self):
+        return self.lmda, self.theta, self.phi
 
 
 @value.value_equality
diff --git a/cirq/contrib/qasm_import/_parser.py b/cirq/contrib/qasm_import/_parser.py
--- a/cirq/contrib/qasm_import/_parser.py
+++ b/cirq/contrib/qasm_import/_parser.py
@@ -186,6 +186,23 @@ def __init__(self):
                           cirq_gate=(lambda params: ops.Rz(params[0])),
                           num_params=1,
                           num_args=1),
+        'id':
+        QasmGateStatement(qasm_gate='id',
+                          cirq_gate=ops.IdentityGate(1),
+                          num_params=0,
+                          num_args=1),
+        'u2':
+        QasmGateStatement(qasm_gate='u2',
+                          cirq_gate=(lambda params: QasmUGate(
+                              0.5, params[0] / np.pi, params[1] / np.pi)),
+                          num_params=2,
+                          num_args=1),
+        'u3':
+        QasmGateStatement(
+            qasm_gate='u3',
+            num_params=3,
+            num_args=1,
+            cirq_gate=(lambda params: QasmUGate(*[p / np.pi for p in params])))
     }
 
     tokens = QasmLexer.tokens
@@ -241,9 +258,9 @@ def p_circuit_reg(self, p):
         p[0] = self.circuit
 
     def p_circuit_gate_or_measurement(self, p):
-        """circuit : gate_op circuit
-                   | measurement circuit"""
-        self.circuit.insert(0, p[1])
+        """circuit :  circuit gate_op
+                   |  circuit measurement"""
+        self.circuit.append(p[2])
         p[0] = self.circuit
 
     def p_circuit_empty(self, p):
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
@@ -622,6 +622,11 @@ def _circuit_diagram_info_(self,
         return protocols.CircuitDiagramInfo(
             wire_symbols=('I',) * self.num_qubits(), connected=True)
 
+    def _qasm_(self, args: protocols.QasmArgs,
+               qubits: Tuple[raw_types.Qid, ...]) -> Optional[str]:
+        args.validate_version('2.0')
+        return ''.join([args.format('id {0};\n', qubit) for qubit in qubits])
+
     def _value_equality_values_(self):
         return self.num_qubits(),
 


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
@@ -34,7 +34,10 @@ def test_u_gate_repr():
 def test_u_gate_eq():
     gate = QasmUGate(0.1, 0.2, 0.3)
     gate2 = QasmUGate(0.1, 0.2, 0.3)
-    assert gate == gate2
+    cirq.approx_eq(gate, gate2, atol=1e-16)
+    gate3 = QasmUGate(0.1, 0.2, 0.4)
+    gate4 = QasmUGate(0.1, 0.2, 2.4)
+    cirq.approx_eq(gate4, gate3, atol=1e-16)
 
 
 def test_qasm_two_qubit_gate_repr():
@@ -216,7 +219,8 @@ class UnsupportedOperation(cirq.Operation):
         _ = str(output)
 
 
-def _all_operations(q0, q1, q2, q3, q4, include_measurments=True):
+def _all_operations(q0, q1, q2, q3, q4, include_measurements=True):
+
     class DummyOperation(cirq.Operation):
         qubits = (q0,)
         with_qubits = NotImplemented
@@ -240,41 +244,34 @@ def __repr__(self):
 
     return (
         cirq.Z(q0),
-        cirq.Z(q0) ** .625,
+        cirq.Z(q0)**.625,
         cirq.Y(q0),
-        cirq.Y(q0) ** .375,
+        cirq.Y(q0)**.375,
         cirq.X(q0),
-        cirq.X(q0) ** .875,
+        cirq.X(q0)**.875,
         cirq.H(q1),
         cirq.CZ(q0, q1),
-        cirq.CZ(q0, q1) ** 0.25,  # Requires 2-qubit decomposition
+        cirq.CZ(q0, q1)**0.25,  # Requires 2-qubit decomposition
         cirq.CNOT(q0, q1),
-        cirq.CNOT(q0, q1) ** 0.5,  # Requires 2-qubit decomposition
+        cirq.CNOT(q0, q1)**0.5,  # Requires 2-qubit decomposition
         cirq.SWAP(q0, q1),
-        cirq.SWAP(q0, q1) ** 0.75,  # Requires 2-qubit decomposition
-
+        cirq.SWAP(q0, q1)**0.75,  # Requires 2-qubit decomposition
         cirq.CCZ(q0, q1, q2),
         cirq.CCX(q0, q1, q2),
         cirq.CCZ(q0, q1, q2)**0.5,
         cirq.CCX(q0, q1, q2)**0.5,
         cirq.CSWAP(q0, q1, q2),
-
+        cirq.IdentityGate(1).on(q0),
+        cirq.IdentityGate(3).on(q0, q1, q2),
         cirq.ISWAP(q2, q0),  # Requires 2-qubit decomposition
-
         cirq.PhasedXPowGate(phase_exponent=0.111, exponent=0.25).on(q1),
         cirq.PhasedXPowGate(phase_exponent=0.333, exponent=0.5).on(q1),
         cirq.PhasedXPowGate(phase_exponent=0.777, exponent=-0.5).on(q1),
-
-        (
-            cirq.measure(q0, key='xX'),
-            cirq.measure(q2, key='x_a'),
-            cirq.measure(q1, key='x?'),
-            cirq.measure(q3, key='X'),
-            cirq.measure(q4, key='_x'),
-            cirq.measure(q2, key='x_a'),
-            cirq.measure(q1, q2, q3, key='multi', invert_mask=(False, True))
-        ) if include_measurments else (),
-
+        (cirq.measure(q0, key='xX'), cirq.measure(q2, key='x_a'),
+         cirq.measure(q1, key='x?'), cirq.measure(q3, key='X'),
+         cirq.measure(q4, key='_x'), cirq.measure(q2, key='x_a'),
+         cirq.measure(q1, q2, q3, key='multi', invert_mask=(False, True)))
+        if include_measurements else (),
         DummyOperation(),
         DummyCompositeOperation(),
     )
@@ -301,7 +298,7 @@ def test_output_parseable_by_qiskit():
 
 def test_output_unitary_same_as_qiskit():
     qubits = tuple(_make_qubits(5))
-    operations = _all_operations(*qubits, include_measurments=False)
+    operations = _all_operations(*qubits, include_measurements=False)
     output = cirq.QasmOutput(operations, qubits,
                              header='Generated from Cirq',
                              precision=10)
@@ -348,9 +345,8 @@ def filter_unpredictable_numbers(text):
     output = cirq.QasmOutput(operations, qubits,
                              header='Generated from Cirq!',
                              precision=5)
-    assert (filter_unpredictable_numbers(str(output)) ==
-            filter_unpredictable_numbers(
-        """// Generated from Cirq!
+    assert (filter_unpredictable_numbers(
+        str(output)) == filter_unpredictable_numbers("""// Generated from Cirq!
 
 OPENQASM 2.0;
 include "qelib1.inc";
@@ -469,6 +465,10 @@ def filter_unpredictable_numbers(text):
 h q[2];
 
 cswap q[0],q[1],q[2];
+id q[0];
+id q[0];
+id q[1];
+id q[2];
 
 // Gate: ISWAP
 cx q[2],q[0];
diff --git a/cirq/contrib/qasm_import/_parser_test.py b/cirq/contrib/qasm_import/_parser_test.py
--- a/cirq/contrib/qasm_import/_parser_test.py
+++ b/cirq/contrib/qasm_import/_parser_test.py
@@ -263,14 +263,15 @@ def test_U_gate():
     q1 = cirq.NamedQubit('q_1')
 
     expected_circuit = Circuit()
-    expected_circuit.append(QasmUGate(1.0, 2.3 / np.pi, 3 / np.pi)(q0))
-
     expected_circuit.append(
         cirq.Moment([
-            QasmUGate(3.14 / np.pi, -1.0, 8 / np.pi)(q0),
+            QasmUGate(1.0, 2.3 / np.pi, 3 / np.pi)(q0),
             QasmUGate(3.14 / np.pi, -1.0, 8 / np.pi)(q1)
         ]))
 
+    expected_circuit.append(
+        cirq.Moment([QasmUGate(3.14 / np.pi, -1.0, 8 / np.pi)(q0)]))
+
     parsed_qasm = parser.parse(qasm)
 
     assert parsed_qasm.supportedFormat
@@ -401,10 +402,12 @@ def test_rotation_gates(qasm_gate: str, cirq_gate: cirq.SingleQubitGate):
     q1 = cirq.NamedQubit('q_1')
 
     expected_circuit = Circuit()
-    expected_circuit.append(cirq_gate(np.pi / 2).on(q0))
     expected_circuit.append(
-        cirq.Moment([cirq_gate(np.pi).on(q0),
+        cirq.Moment([cirq_gate(np.pi / 2).on(q0),
                      cirq_gate(np.pi).on(q1)]))
+    expected_circuit.append(cirq.Moment([
+        cirq_gate(np.pi).on(q0),
+    ]))
 
     parsed_qasm = parser.parse(qasm)
 
@@ -448,22 +451,6 @@ def test_rotation_gates_zero_params_error(qasm_gate: str):
         parser.parse(qasm)
 
 
-@pytest.mark.parametrize('qasm_gate', [g[0] for g in rotation_gates])
-def test_rotation_gates_too_many_params_error(qasm_gate: str):
-    qasm = """OPENQASM 2.0;
-     include "qelib1.inc";             
-     qreg q[2];     
-     {}(pi, 2*pi) q[1];     
-""".format(qasm_gate)
-
-    parser = QasmParser()
-
-    with pytest.raises(
-            QasmException,
-            match=r".*{}.* takes 1.*got.*2.*line 4".format(qasm_gate)):
-        parser.parse(qasm)
-
-
 def test_qelib_gate_without_include_statement():
     qasm = """OPENQASM 2.0;
          qreg q[2];
@@ -515,10 +502,10 @@ def test_measure_individual_bits():
 
     expected_circuit = Circuit()
 
-    expected_circuit.append(
-        cirq.MeasurementGate(num_qubits=1, key='c1_1').on(q1_1))
     expected_circuit.append(
         cirq.MeasurementGate(num_qubits=1, key='c1_0').on(q1_0))
+    expected_circuit.append(
+        cirq.MeasurementGate(num_qubits=1, key='c1_1').on(q1_1))
 
     parsed_qasm = parser.parse(qasm)
 
@@ -635,3 +622,140 @@ def test_measurement_bounds():
     with pytest.raises(QasmException,
                        match=r"Out of bounds bit.*4.*c1.*size 3.*line 4"):
         parser.parse(qasm)
+
+
+def test_u2_gate():
+    qasm = """
+     OPENQASM 2.0;
+     include "qelib1.inc";
+     qreg q[1];
+     u2(2 * pi, pi / 3.0) q[0];    
+"""
+    parser = QasmParser()
+
+    q0 = cirq.NamedQubit('q_0')
+
+    expected_circuit = Circuit()
+    expected_circuit.append(QasmUGate(0.5, 2.0, 1.0 / 3.0)(q0))
+
+    parsed_qasm = parser.parse(qasm)
+
+    assert parsed_qasm.supportedFormat
+    assert parsed_qasm.qelib1Include
+
+    ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit)
+    assert parsed_qasm.qregs == {'q': 1}
+
+
+def test_id_gate():
+    qasm = """
+     OPENQASM 2.0;
+     include "qelib1.inc";
+     qreg q[2];
+     id q;           
+"""
+    parser = QasmParser()
+
+    q0 = cirq.NamedQubit('q_0')
+    q1 = cirq.NamedQubit('q_1')
+
+    expected_circuit = Circuit()
+    expected_circuit.append(cirq.IdentityGate(num_qubits=1)(q0))
+    expected_circuit.append(cirq.IdentityGate(num_qubits=1)(q1))
+
+    parsed_qasm = parser.parse(qasm)
+
+    assert parsed_qasm.supportedFormat
+    assert parsed_qasm.qelib1Include
+
+    ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit)
+    assert parsed_qasm.qregs == {'q': 2}
+
+
+def test_u3_gate():
+    qasm = """
+     OPENQASM 2.0;
+     include "qelib1.inc";
+     qreg q[2];
+     u3(pi, 2.3, 3) q[0];
+     u3(+3.14, -pi, (8)) q;
+"""
+    parser = QasmParser()
+
+    q0 = cirq.NamedQubit('q_0')
+    q1 = cirq.NamedQubit('q_1')
+
+    expected_circuit = Circuit()
+    expected_circuit.append(
+        cirq.Moment([
+            QasmUGate(1.0, 2.3 / np.pi, 3 / np.pi)(q0),
+            QasmUGate(3.14 / np.pi, -1.0, 8 / np.pi)(q1),
+        ]))
+
+    expected_circuit.append(
+        cirq.Moment([QasmUGate(3.14 / np.pi, -1.0, 8 / np.pi)(q0)]))
+
+    parsed_qasm = parser.parse(qasm)
+
+    assert parsed_qasm.supportedFormat
+    assert parsed_qasm.qelib1Include
+
+    ct.assert_same_circuits(parsed_qasm.circuit, expected_circuit)
+    assert parsed_qasm.qregs == {'q': 2}
+
+
+@pytest.mark.parametrize('qasm_gate', [
+    'id',
+    'u2',
+    'u3',
+] + [g[0] for g in rotation_gates])
+def test_standard_single_qubit_gates_wrong_number_of_args(qasm_gate):
+    qasm = """
+     OPENQASM 2.0;
+     include "qelib1.inc";             
+     qreg q[2];     
+     {} q[0], q[1];     
+""".format(qasm_gate)
+
+    parser = QasmParser()
+
+    with pytest.raises(QasmException, match=r".* takes 1.*got.*2.*line 5"):
+        parser.parse(qasm)
+
+
+@pytest.mark.parametrize(['qasm_gate', 'num_params'], [
+    ['id', 0],
+    ['u2', 2],
+    ['u3', 3],
+    ['rx', 1],
+    ['ry', 1],
+    ['rz', 1],
+])
+def test_standard_gates_wrong_params_error(qasm_gate: str, num_params: int):
+    qasm = """OPENQASM 2.0;
+     include "qelib1.inc";             
+     qreg q[2];     
+     {}(pi, 2*pi, 3*pi, 4*pi, 5*pi) q[1];     
+""".format(qasm_gate)
+
+    parser = QasmParser()
+
+    with pytest.raises(QasmException,
+                       match=r".*{}.* takes {}.*got.*5.*line 4".format(
+                           qasm_gate, num_params)):
+        parser.parse(qasm)
+
+    if num_params == 0: return
+
+    qasm = """OPENQASM 2.0;
+     include "qelib1.inc";             
+     qreg q[2];     
+     {}() q[1];     
+    """.format(qasm_gate)
+
+    parser = QasmParser()
+
+    with pytest.raises(QasmException,
+                       match=r".*{}.* takes {}.*got.*0.*line 4".format(
+                           qasm_gate, num_params)):
+        parser.parse(qasm)
diff --git a/cirq/contrib/qasm_import/qasm_test.py b/cirq/contrib/qasm_import/qasm_test.py
--- a/cirq/contrib/qasm_import/qasm_test.py
+++ b/cirq/contrib/qasm_import/qasm_test.py
@@ -11,12 +11,24 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+import numpy as np
+
 import cirq
+import cirq.testing as ct
 
 
 # TODO as we grow the language, we'll add more complex examples here
 def test_consistency_with_qasm_output():
-    circuit1 = cirq.Circuit()
+    a, b, c, d = [cirq.NamedQubit('q_{}'.format(i)) for i in range(4)]
+    circuit1 = cirq.Circuit.from_ops(
+        cirq.Rx(np.pi / 2).on(a),
+        cirq.Ry(np.pi / 2).on(b),
+        cirq.Rz(np.pi / 2).on(b),
+        cirq.IdentityGate(1).on(c),
+        cirq.circuits.qasm_output.QasmUGate(1.0, 2.0, 3.0).on(d),
+    )
+
     qasm1 = cirq.qasm(circuit1)
+
     circuit2 = cirq.contrib.qasm_import.qasm.QasmCircuitParser().parse(qasm1)
-    cirq.testing.assert_same_circuits(circuit1, circuit2)
+    ct.assert_same_circuits(circuit1, circuit2)