Source code for pytket.extensions.cirq.backends.cirq_convert

# Copyright 2019-2024 Quantinuum
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# 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.

"""
Methods to allow conversion between Cirq and tket data types, including Circuits and
Devices
"""

from typing import List, Dict, FrozenSet, cast, Any, Union, Type
import cmath
from logging import warning
import re
from cirq.devices import LineQubit, GridQubit
import cirq.ops
import cirq_google
from sympy import pi, Basic, Symbol

from pytket.circuit import Circuit, OpType, Qubit, Bit, Node
from pytket.architecture import Architecture

# For translating cirq circuits to tket circuits
cirq_common = cirq.ops.common_gates
cirq_pauli = cirq.ops.pauli_gates

cirq_CH = cirq_common.H.controlled(1)

# map cirq common gates to pytket gates
_cirq2ops_mapping = {
    cirq_common.CNOT: OpType.CX,
    cirq_common.H: OpType.H,
    cirq_common.MeasurementGate: OpType.Measure,
    cirq_common.XPowGate: OpType.Rx,
    cirq_common.YPowGate: OpType.Ry,
    cirq_common.ZPowGate: OpType.Rz,
    cirq_common.XPowGate(exponent=0.5): OpType.V,
    cirq_common.XPowGate(exponent=-0.5): OpType.Vdg,
    cirq_common.S: OpType.S,
    cirq_common.SWAP: OpType.SWAP,
    cirq_common.T: OpType.T,
    cirq_pauli.X: OpType.X,
    cirq_pauli.Y: OpType.Y,
    cirq_pauli.Z: OpType.Z,
    cirq.ops.I: OpType.noop,
    cirq_common.CZPowGate: OpType.CU1,
    cirq_common.CZ: OpType.CZ,
    cirq_CH: OpType.CH,
    cirq.ops.CSwapGate: OpType.CSWAP,
    cirq_common.ISwapPowGate: OpType.ISWAP,
    cirq_common.ISWAP: OpType.ISWAPMax,
    cirq.ops.FSimGate: OpType.FSim,
    cirq_google.SYC: OpType.Sycamore,
    cirq.ops.parity_gates.ZZPowGate: OpType.ZZPhase,
    cirq.ops.parity_gates.XXPowGate: OpType.XXPhase,
    cirq.ops.parity_gates.YYPowGate: OpType.YYPhase,
    cirq.ops.PhasedXPowGate: OpType.PhasedX,
    cirq.ops.PhasedISwapPowGate: OpType.PhasedISWAP,
}
# reverse mapping for convenience
_ops2cirq_mapping: Dict = dict((item[1], item[0]) for item in _cirq2ops_mapping.items())
# spot special rotation gates
_constant_gates = (
    cirq_common.CNOT,
    cirq_common.H,
    cirq_common.S,
    cirq_common.SWAP,
    cirq_common.T,
    cirq_pauli.X,
    cirq_pauli.Y,
    cirq_pauli.Z,
    cirq_common.CZ,
    cirq_CH,
    cirq_common.ISWAP,
    cirq_google.SYC,
    cirq.ops.I,
)

_radian_gates = (
    cirq_common.Rx,
    cirq_common.Ry,
    cirq_common.Rz,
)
_cirq2ops_radians_mapping = {
    cirq_common.Rx: OpType.Rx,
    cirq_common.Ry: OpType.Ry,
    cirq_common.Rz: OpType.Rz,
}


[docs] def cirq_to_tk(circuit: cirq.circuits.Circuit) -> Circuit: """Converts a Cirq :py:class:`Circuit` to a tket :py:class:`Circuit` object. :param circuit: The input Cirq :py:class:`Circuit` :raises NotImplementedError: If the input contains a Cirq :py:class:`Circuit` operation which is not yet supported by pytket :return: The tket :py:class:`Circuit` corresponding to the input circuit """ tkcirc = Circuit() qmap = {} for qb in circuit.all_qubits(): if isinstance(qb, LineQubit): uid = Qubit("q", qb.x) elif isinstance(qb, GridQubit): uid = Qubit("g", qb.row, qb.col) elif isinstance(qb, cirq.ops.NamedQubit): uid = Qubit(qb.name) else: raise NotImplementedError("Cannot convert qubits of type " + str(type(qb))) tkcirc.add_qubit(uid) qmap.update({qb: uid}) for moment in circuit: for op in moment.operations: gate = op.gate gatetype = type(gate) qb_lst = [qmap[q] for q in op.qubits] if isinstance(gate, cirq.ops.global_phase_op.GlobalPhaseGate): tkcirc.add_phase(cmath.phase(gate.coefficient) / pi) continue if isinstance(gate, cirq_common.HPowGate) and gate.exponent == 1: gate = cirq_common.H elif ( gatetype == cirq_common.CNotPowGate and cast(cirq_common.CNotPowGate, gate).exponent == 1 ): gate = cirq_common.CNOT elif ( gatetype == cirq_pauli._PauliX and cast(cirq_pauli._PauliX, gate).exponent == 1 ): gate = cirq_pauli.X elif ( gatetype == cirq_pauli._PauliY and cast(cirq_pauli._PauliY, gate).exponent == 1 ): gate = cirq_pauli.Y elif ( gatetype == cirq_pauli._PauliZ and cast(cirq_pauli._PauliZ, gate).exponent == 1 ): gate = cirq_pauli.Z apply_in_parallel = False if isinstance(gate, cirq.ops.ParallelGate): if gate.num_copies != len(qb_lst): raise NotImplementedError( "ParallelGate parameters defined incorrectly." ) gate = gate.sub_gate gatetype = type(gate) apply_in_parallel = True if gate in _constant_gates: try: optype = _cirq2ops_mapping[gate] except KeyError as error: raise NotImplementedError( "Operation not supported by tket: " + str(op.gate) ) from error params: List[Union[float, Basic, Symbol]] = [] elif gatetype in _radian_gates: try: optype = _cirq2ops_radians_mapping[ cast(Type[cirq.ops.EigenGate], gatetype) ] except KeyError as error: raise NotImplementedError( "Operation not supported by tket: " + str(op.gate) ) from error params = [gate._rads / pi] # type: ignore elif isinstance(gate, cirq_common.MeasurementGate): # Adding "_b" to the bit uid since for cirq.NamedQubit, # the gate.key is equal to the qubit id (the qubit name) bitid = Bit(gate.key + "_b") tkcirc.add_bit(bitid) assert len(qb_lst) == 1 tkcirc.Measure(qb_lst[0], bitid) continue elif isinstance(gate, cirq.ops.PhasedXPowGate): optype = OpType.PhasedX pe = gate.phase_exponent params = [gate.exponent, pe] elif isinstance(gate, cirq.ops.FSimGate): optype = OpType.FSim params = [gate.theta / pi, gate.phi / pi] elif isinstance(gate, cirq.ops.PhasedISwapPowGate): optype = OpType.PhasedISWAP params = [gate.phase_exponent, gate.exponent] else: try: optype = _cirq2ops_mapping[gatetype] params = [cast(Any, gate).exponent] except (KeyError, AttributeError) as error: raise NotImplementedError( "Operation not supported by tket: " + str(op.gate) ) from error if apply_in_parallel: for qbit in qb_lst: tkcirc.add_gate(optype, params, [qbit]) else: tkcirc.add_gate(optype, params, qb_lst) return tkcirc
[docs] def tk_to_cirq(tkcirc: Circuit, copy_all_qubits: bool = False) -> cirq.circuits.Circuit: """Converts a tket :py:class:`Circuit` object to a Cirq :py:class:`Circuit`. :param tkcirc: The input tket :py:class:`Circuit` :return: The Cirq :py:class:`Circuit` corresponding to the input circuit """ if copy_all_qubits: tkcirc = tkcirc.copy() for q in tkcirc.qubits: tkcirc.add_gate(OpType.noop, [q]) qmap: Dict[Qubit, Union[cirq.ops.NamedQubit, LineQubit, GridQubit]] = {} line_name = None grid_name = None # Since Cirq can only support registers of up to 2 dimensions, we explicitly # check for 3-dimensional registers whose third dimension is trivial. # SquareGrid architectures are of this form. indices = [qb.index for qb in tkcirc.qubits] is_flat_3d = all(idx[2] == 0 for idx in indices if len(idx) == 3) for qb in tkcirc.qubits: if len(qb.index) == 0: qmap.update({qb: cirq.ops.NamedQubit(qb.reg_name)}) elif len(qb.index) == 1: if line_name != None and line_name != qb.reg_name: raise NotImplementedError( "Cirq can only support a single linear register" ) line_name = qb.reg_name qmap.update({qb: LineQubit(qb.index[0])}) elif len(qb.index) == 2 or (len(qb.index) == 3 and is_flat_3d): if grid_name != None and grid_name != qb.reg_name: raise NotImplementedError( "Cirq can only support a single grid register" ) grid_name = qb.reg_name qmap.update({qb: GridQubit(qb.index[0], qb.index[1])}) else: raise NotImplementedError( "Cirq can only support registers of dimension <=2" ) oplst = [] for command in tkcirc: op = command.op optype = op.type try: gatetype = _ops2cirq_mapping[optype] except KeyError as error: raise NotImplementedError( "Cannot convert tket Op to Cirq gate: " + op.get_name() ) from error if optype == OpType.Measure: qid = qmap[cast(Qubit, command.args[0])] bit = command.args[1] # Removing the "_b" added to measurement bit registers uids, # for dealing with NamedQubits bit_repr = bit.__repr__() if re.search("_b$", bit_repr): bit_repr = bit_repr[0:-2] cirqop = cirq.ops.measure(qid, key=bit_repr) else: qids = [qmap[cast(Qubit, qbit)] for qbit in command.args] params = op.params if len(params) == 0: cirqop = gatetype(*qids) elif optype == OpType.PhasedX: cirqop = gatetype(phase_exponent=params[1], exponent=params[0])(*qids) elif optype == OpType.FSim: cirqop = gatetype( theta=float(params[0] * pi), phi=float(params[1] * pi) )(*qids) elif optype == OpType.PhasedISWAP: cirqop = gatetype(phase_exponent=params[0], exponent=params[1])(*qids) else: cirqop = gatetype(exponent=params[0])(*qids) oplst.append(cirqop) try: coeff = cmath.exp(float(tkcirc.phase) * cmath.pi * 1j) if coeff.real < 1e-8: # tolerance permitted by cirq for GlobalPhaseGate coeff = coeff.imag * 1j if coeff.imag < 1e-8: coeff = coeff.real if coeff != 1.0: oplst.append(cirq.ops.global_phase_operation(coeff)) except ValueError: warning( "Global phase is dependent on a symbolic parameter, so cannot adjust for " "phase" ) return cirq.circuits.Circuit(*oplst)
# For converting cirq devices to tket devices def _sort_row_col(qubits: FrozenSet[GridQubit]) -> List[GridQubit]: """Sort grid qubits first by row then by column""" return sorted(qubits, key=lambda x: (x.row, x.col))
[docs] def process_characterisation( device: cirq_google.devices.grid_device.GridDevice, ) -> dict: """Generates a tket dictionary containing device characteristics for a Cirq :py:class:`GridDevice`. :param device: The device to convert :return: A dictionary containing device characteristics """ data = device.metadata qubits: FrozenSet[GridQubit] = data.qubit_set qubit_graph = data.nx_graph qb_map = {q: Node("q", q.row, q.col) for q in qubits} indexed_qubits = _sort_row_col(qubits) coupling_map = [] for qb in indexed_qubits: for x in qubit_graph.neighbors(qb): coupling_map.append((qb_map[qb], qb_map[x])) arc = Architecture(coupling_map) characterisation = dict() characterisation["Architecture"] = arc return characterisation