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

# Copyright 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.

import cmath
import re
from logging import warning
from typing import Any, cast

import cirq_google
from sympy import Basic, Symbol, pi

import cirq.ops
from cirq.devices import GridQubit, LineQubit
from pytket.architecture import Architecture
from pytket.circuit import Bit, Circuit, Node, OpType, Qubit

# 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,
    cirq.ops.common_channels.ResetChannel: OpType.Reset,
}
# reverse mapping for convenience
_ops2cirq_mapping: 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: # noqa: PLR0912, PLR0915 """Converts a Cirq :py:class:`cirq.Circuit` to a tket :py:class:`~.pytket._tket.circuit.Circuit` object. :param circuit: The input Cirq :py:class:`cirq.Circuit` :raises NotImplementedError: If the input contains a Cirq :py:class:`cirq.Circuit` operation which is not yet supported by pytket :return: The tket :py:class:`~.pytket._tket.circuit.Circuit` corresponding to the input circuit """ tkcirc = Circuit() qmap: dict[cirq.ops.Qid, Qubit] = {} 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 # noqa: SLF001 and cast("cirq_pauli._PauliX", gate).exponent == 1 # noqa: SLF001 ): gate = cirq_pauli.X elif ( gatetype == cirq_pauli._PauliY # noqa: SLF001 and cast("cirq_pauli._PauliY", gate).exponent == 1 # noqa: SLF001 ): gate = cirq_pauli.Y elif ( gatetype == cirq_pauli._PauliZ # noqa: SLF001 and cast("cirq_pauli._PauliZ", gate).exponent == 1 # noqa: SLF001 ): 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[float | Basic | Symbol] = [] elif isinstance(gate, cirq.ops.common_channels.ResetChannel): optype = OpType.Reset params = [] 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 # noqa: SLF001 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: # noqa: PLR0912, PLR0915 """Converts a tket :py:class:`~.pytket._tket.circuit.Circuit` object to a Cirq :py:class:`cirq.Circuit`. :param tkcirc: The input tket :py:class:`~.pytket._tket.circuit.Circuit` :return: The Cirq :py:class:`cirq.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, 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) # noqa: PLR2004 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: # noqa: E711, PLR1714 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): # noqa: PLR2004 if grid_name != None and grid_name != qb.reg_name: # noqa: E711, PLR1714 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) elif optype == OpType.Reset: qid = qmap[cast("Qubit", command.args[0])] cirqop = cirq.ops.ResetChannel().on(qid) 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 # noqa: PLR2004 ): # tolerance permitted by cirq for GlobalPhaseGate coeff = coeff.imag * 1j if coeff.imag < 1e-8: # noqa: PLR2004 coeff = coeff.real if coeff != 1.0: oplst.append(cirq.ops.global_phase_operation(coeff)) except ValueError: warning( # noqa: LOG015 "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:`cirq.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])) # noqa: PERF401 arc = Architecture(coupling_map) characterisation = dict() # noqa: C408 characterisation["Architecture"] = arc return characterisation