1# Copyright 2020 The Cirq Developers
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from typing import Callable, Dict, Union
16
17import numpy as np
18from pyquil.parser import parse
19from pyquil.quilbase import (
20    Declare,
21    DefGate,
22    Gate as PyQuilGate,
23    Measurement as PyQuilMeasurement,
24    Pragma,
25    Reset,
26    ResetQubit,
27)
28
29from cirq import Circuit, LineQubit
30from cirq.ops import (
31    CCNOT,
32    CNOT,
33    CSWAP,
34    CZ,
35    CZPowGate,
36    Gate,
37    H,
38    I,
39    ISWAP,
40    ISwapPowGate,
41    MatrixGate,
42    MeasurementGate,
43    S,
44    SWAP,
45    T,
46    TwoQubitDiagonalGate,
47    X,
48    Y,
49    Z,
50    ZPowGate,
51    rx,
52    ry,
53    rz,
54)
55
56
57class UndefinedQuilGate(Exception):
58    pass
59
60
61class UnsupportedQuilInstruction(Exception):
62    pass
63
64
65#
66# Functions for converting supported parameterized Quil gates.
67#
68
69
70def cphase(param: float) -> CZPowGate:
71    """Returns a controlled-phase gate as a Cirq CZPowGate with exponent
72    determined by the input param. The angle parameter of pyQuil's CPHASE
73    gate and the exponent of Cirq's CZPowGate differ by a factor of pi.
74
75    Args:
76        param: Gate parameter (in radians).
77
78    Returns:
79        A CZPowGate equivalent to a CPHASE gate of given angle.
80    """
81    return CZPowGate(exponent=param / np.pi)
82
83
84def cphase00(phi: float) -> TwoQubitDiagonalGate:
85    """Returns a Cirq TwoQubitDiagonalGate for pyQuil's CPHASE00 gate.
86
87    In pyQuil, CPHASE00(phi) = diag([exp(1j * phi), 1, 1, 1]), and in Cirq,
88    a TwoQubitDiagonalGate is specified by its diagonal in radians, which
89    would be [phi, 0, 0, 0].
90
91    Args:
92        phi: Gate parameter (in radians).
93
94    Returns:
95        A TwoQubitDiagonalGate equivalent to a CPHASE00 gate of given angle.
96    """
97    return TwoQubitDiagonalGate([phi, 0, 0, 0])
98
99
100def cphase01(phi: float) -> TwoQubitDiagonalGate:
101    """Returns a Cirq TwoQubitDiagonalGate for pyQuil's CPHASE01 gate.
102
103    In pyQuil, CPHASE01(phi) = diag(1, [exp(1j * phi), 1, 1]), and in Cirq,
104    a TwoQubitDiagonalGate is specified by its diagonal in radians, which
105    would be [0, phi, 0, 0].
106
107    Args:
108        phi: Gate parameter (in radians).
109
110    Returns:
111        A TwoQubitDiagonalGate equivalent to a CPHASE01 gate of given angle.
112    """
113    return TwoQubitDiagonalGate([0, phi, 0, 0])
114
115
116def cphase10(phi: float) -> TwoQubitDiagonalGate:
117    """Returns a Cirq TwoQubitDiagonalGate for pyQuil's CPHASE10 gate.
118
119    In pyQuil, CPHASE10(phi) = diag(1, 1, [exp(1j * phi), 1]), and in Cirq,
120    a TwoQubitDiagonalGate is specified by its diagonal in radians, which
121    would be [0, 0, phi, 0].
122
123    Args:
124        phi: Gate parameter (in radians).
125
126    Returns:
127        A TwoQubitDiagonalGate equivalent to a CPHASE10 gate of given angle.
128    """
129    return TwoQubitDiagonalGate([0, 0, phi, 0])
130
131
132def phase(param: float) -> ZPowGate:
133    """Returns a single-qubit phase gate as a Cirq ZPowGate with exponent
134    determined by the input param. The angle parameter of pyQuil's PHASE
135    gate and the exponent of Cirq's ZPowGate differ by a factor of pi.
136
137    Args:
138        param: Gate parameter (in radians).
139
140    Returns:
141        A ZPowGate equivalent to a PHASE gate of given angle.
142    """
143    return ZPowGate(exponent=param / np.pi)
144
145
146def pswap(phi: float) -> MatrixGate:
147    """Returns a Cirq MatrixGate for pyQuil's PSWAP gate.
148
149    Args:
150        phi: Gate parameter (in radians).
151
152    Returns:
153        A MatrixGate equivalent to a PSWAP gate of given angle.
154    """
155    pswap_matrix = np.array(
156        [
157            [1, 0, 0, 0],
158            [0, 0, np.exp(1j * phi), 0],
159            [0, np.exp(1j * phi), 0, 0],
160            [0, 0, 0, 1],
161        ],
162        dtype=complex,
163    )
164    return MatrixGate(pswap_matrix)
165
166
167def xy(param: float) -> ISwapPowGate:
168    """Returns an ISWAP-family gate as a Cirq ISwapPowGate with exponent
169    determined by the input param. The angle parameter of pyQuil's XY gate
170    and the exponent of Cirq's ISwapPowGate differ by a factor of pi.
171
172    Args:
173        param: Gate parameter (in radians).
174
175    Returns:
176        An ISwapPowGate equivalent to an XY gate of given angle.
177    """
178    return ISwapPowGate(exponent=param / np.pi)
179
180
181PRAGMA_ERROR = """
182Please remove PRAGMAs from your Quil program.
183If you would like to add noise, do so after conversion.
184"""
185
186RESET_ERROR = """
187Please remove RESETs from your Quil program.
188RESET directives have special meaning on QCS, to enable active reset.
189"""
190
191# Parameterized gates map to functions that produce Gate constructors.
192SUPPORTED_GATES: Dict[str, Union[Gate, Callable[..., Gate]]] = {
193    "CCNOT": CCNOT,
194    "CNOT": CNOT,
195    "CSWAP": CSWAP,
196    "CPHASE": cphase,
197    "CPHASE00": cphase00,
198    "CPHASE01": cphase01,
199    "CPHASE10": cphase10,
200    "CZ": CZ,
201    "PHASE": phase,
202    "H": H,
203    "I": I,
204    "ISWAP": ISWAP,
205    "PSWAP": pswap,
206    "RX": rx,
207    "RY": ry,
208    "RZ": rz,
209    "S": S,
210    "SWAP": SWAP,
211    "T": T,
212    "X": X,
213    "Y": Y,
214    "Z": Z,
215    "XY": xy,
216}
217
218
219def circuit_from_quil(quil: str) -> Circuit:
220    """Convert a Quil program to a Cirq Circuit.
221
222    Args:
223        quil: The Quil program to convert.
224
225    Returns:
226        A Cirq Circuit generated from the Quil program.
227
228    Raises:
229        UnsupportedQuilInstruction: Cirq does not support the specified Quil instruction.
230        UndefinedQuilGate: Cirq does not support the specified Quil gate.
231
232    References:
233        https://github.com/rigetti/pyquil
234    """
235    circuit = Circuit()
236    defined_gates = SUPPORTED_GATES.copy()
237    instructions = parse(quil)
238
239    for inst in instructions:
240        # Add DEFGATE-defined gates to defgates dict using MatrixGate.
241        if isinstance(inst, DefGate):
242            if inst.parameters:
243                raise UnsupportedQuilInstruction(
244                    "Parameterized DEFGATEs are currently unsupported."
245                )
246            defined_gates[inst.name] = MatrixGate(inst.matrix)
247
248        # Pass when encountering a DECLARE.
249        elif isinstance(inst, Declare):
250            pass
251
252        # Convert pyQuil gates to Cirq operations.
253        elif isinstance(inst, PyQuilGate):
254            quil_gate_name = inst.name
255            quil_gate_params = inst.params
256            line_qubits = list(LineQubit(q.index) for q in inst.qubits)
257            if quil_gate_name not in defined_gates:
258                raise UndefinedQuilGate(f"Quil gate {quil_gate_name} not supported in Cirq.")
259            cirq_gate_fn = defined_gates[quil_gate_name]
260            if quil_gate_params:
261                circuit += cirq_gate_fn(*quil_gate_params)(*line_qubits)
262            else:
263                circuit += cirq_gate_fn(*line_qubits)
264
265        # Convert pyQuil MEASURE operations to Cirq MeasurementGate objects.
266        elif isinstance(inst, PyQuilMeasurement):
267            line_qubit = LineQubit(inst.qubit.index)
268            if inst.classical_reg is None:
269                raise UnsupportedQuilInstruction(
270                    f"Quil measurement {inst} without classical register "
271                    f"not currently supported in Cirq."
272                )
273            quil_memory_reference = inst.classical_reg.out()
274            circuit += MeasurementGate(1, key=quil_memory_reference)(line_qubit)
275
276        # Raise a targeted error when encountering a PRAGMA.
277        elif isinstance(inst, Pragma):
278            raise UnsupportedQuilInstruction(PRAGMA_ERROR)
279
280        # Raise a targeted error when encountering a RESET.
281        elif isinstance(inst, (Reset, ResetQubit)):
282            raise UnsupportedQuilInstruction(RESET_ERROR)
283
284        # Raise a general error when encountering an unconsidered type.
285        else:
286            raise UnsupportedQuilInstruction(
287                f"Quil instruction {inst} of type {type(inst)} not currently supported in Cirq."
288            )
289
290    return circuit
291