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