Source code for graphix.transpiler

"""Gate-to-MBQC transpiler.

accepts desired gate operations and transpile into MBQC measurement patterns.

"""

from __future__ import annotations

import enum
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, Generic, SupportsFloat, TypeVar, overload

import networkx as nx

# assert_never introduced in Python 3.11
# override introduced in Python 3.12
from typing_extensions import assert_never, override

from graphix import Instruction, command, parameter
from graphix.branch_selector import BranchSelector, RandomBranchSelector
from graphix.flow.core import CausalFlow, _corrections_to_partial_order_layers
from graphix.fundamentals import ANGLE_PI, Axis
from graphix.instruction import InstructionKind, InstructionVisitor
from graphix.measurements import BlochMeasurement, Measurement, Outcome, PauliMeasurement
from graphix.opengraph import OpenGraph
from graphix.ops import Ops
from graphix.parameter import InplaceParameterizable
from graphix.pattern import Pattern
from graphix.sim.base_backend import DenseStateBackend
from graphix.sim.density_matrix import DensityMatrixBackend
from graphix.sim.statevec import Statevector, StatevectorBackend

if TYPE_CHECKING:
    from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
    from typing import Literal

    from numpy.random import Generator

    from graphix.command import Node
    from graphix.fundamentals import ParameterizedAngle
    from graphix.instruction import InstructionType
    from graphix.parameter import ExpressionOrSupportsFloat, Parameter
    from graphix.pattern import Pattern
    from graphix.sim import Data
    from graphix.sim.base_backend import DenseState, Matrix
    from graphix.sim.density_matrix import DensityMatrix

    _BuiltinDenseStateBackend = DensityMatrixBackend | StatevectorBackend
    _DenseStateBackendLiteral = Literal["statevector", "densitymatrix"]

_DenseStateT = TypeVar("_DenseStateT", bound="DenseState")


[docs] @dataclass(frozen=True, slots=True) class TranspiledPattern: """A transpiled pattern.""" pattern: Pattern classical_outputs: tuple[Node, ...] """Nodes measured with circuit measurements, in the order of the gates."""
[docs] @dataclass(frozen=True, slots=True) class TranspiledFlow: """A transpiled causal flow.""" flow: CausalFlow[BlochMeasurement] classical_outputs: dict[int, command.M] """M commands for nodes measured with circuit measurements."""
[docs] def to_pattern(self) -> TranspiledPattern: """Return the transpiled pattern.""" pattern = self.flow.to_standardizedpattern().to_space_optimal_pattern() pattern.extend(self.classical_outputs.values()) return TranspiledPattern(pattern, tuple(self.classical_outputs.keys()))
[docs] @dataclass(frozen=True) class SimulateResult(Generic[_DenseStateT]): """ Result of a circuit simulation. state : _DenseStateT State representation of the simulation output. classical_measures : tuple[int,...] Results of classical measurements. """ state: _DenseStateT # mypy rejects covariant types as dataclass parameters as of Python 3.13 classical_measures: tuple[int, ...]
@dataclass class _MapAngleVisitor(InstructionVisitor): f: Callable[[ParameterizedAngle], ParameterizedAngle] @override def visit_angle(self, angle: ParameterizedAngle) -> ParameterizedAngle: return self.f(angle)
[docs] class Circuit(InplaceParameterizable): """Gate-to-MBQC transpiler. Holds gate operations and translates into MBQC measurement patterns. Attributes ---------- width : int Number of logical qubits (for gate network) instruction : list List containing the gate sequence applied. """ instruction: list[InstructionType]
[docs] def __init__(self, width: int, instr: Iterable[InstructionType] | None = None) -> None: """ Construct a circuit. Parameters ---------- width : int number of logical qubits for the gate network instr : list[InstructionType] | None Optional. List of initial instructions. """ self.width = width self.instruction = [] self.active_qubits = set(range(width)) if instr is not None: self.extend(instr)
[docs] def add(self, instr: InstructionType) -> None: """Add an instruction to the circuit.""" match instr.kind: case InstructionKind.CCX: self.ccx(instr.controls[0], instr.controls[1], instr.target) case InstructionKind.RZZ: self.rzz(instr.control, instr.target, instr.angle) case InstructionKind.CNOT: self.cnot(instr.control, instr.target) case InstructionKind.SWAP: self.swap(instr.targets[0], instr.targets[1]) case InstructionKind.CZ: self.cz(instr.targets[0], instr.targets[1]) case InstructionKind.H: self.h(instr.target) case InstructionKind.S: self.s(instr.target) case InstructionKind.X: self.x(instr.target) case InstructionKind.Y: self.y(instr.target) case InstructionKind.Z: self.z(instr.target) case InstructionKind.I: self.i(instr.target) case InstructionKind.M: self.m(instr.target, instr.axis) case InstructionKind.RX: self.rx(instr.target, instr.angle) case InstructionKind.RY: self.ry(instr.target, instr.angle) case InstructionKind.RZ: self.rz(instr.target, instr.angle) case InstructionKind.J: self.j(instr.target, instr.angle) case InstructionKind.SDG: self.sdg(instr.target) case InstructionKind.T: self.t(instr.target) case InstructionKind.TDG: self.tdg(instr.target) case InstructionKind.SX: self.sx(instr.target) case InstructionKind.SXDG: self.sxdg(instr.target) case InstructionKind.CY: self.cy(instr.control, instr.target) case InstructionKind.P: self.p(instr.target, instr.angle) case InstructionKind.U: self.u(instr.target, instr.theta, instr.phi, instr.lambda_) case InstructionKind.CJ: self.cj(instr.control, instr.target, instr.angle) case InstructionKind.CP: self.cp(instr.control, instr.target, instr.angle) case InstructionKind.CRX: self.crx(instr.control, instr.target, instr.angle) case InstructionKind.CRY: self.cry(instr.control, instr.target, instr.angle) case InstructionKind.CRZ: self.crz(instr.control, instr.target, instr.angle) case InstructionKind.CU: self.cu(instr.control, instr.target, instr.theta, instr.phi, instr.lambda_, instr.gamma) case InstructionKind.CSWAP: self.cswap(instr.control, instr.targets[0], instr.targets[1]) case InstructionKind.GPHASE: self.gphase(instr.angle) case _: assert_never(instr.kind)
[docs] def extend(self, instrs: Iterable[InstructionType]) -> None: """Add instructions to the circuit.""" for instr in instrs: self.add(instr)
def __repr__(self) -> str: """Return a representation of the Circuit.""" return f"Circuit(width={self.width}, instr={self.instruction})"
[docs] def cnot(self, control: int, target: int) -> None: """Apply a CNOT gate. See :class:`~graphix.instruction.CNOT` for more information. Parameters ---------- control : int control qubit target : int target qubit """ assert control in self.active_qubits assert target in self.active_qubits assert control != target self.instruction.append(Instruction.CNOT(control=control, target=target))
[docs] def swap(self, qubit1: int, qubit2: int) -> None: """Apply a SWAP gate. See :class:`~graphix.instruction.SWAP` for more information. Parameters ---------- qubit1 : int first qubit to be swapped qubit2 : int second qubit to be swapped """ assert qubit1 in self.active_qubits assert qubit2 in self.active_qubits assert qubit1 != qubit2 self.instruction.append(Instruction.SWAP(targets=(qubit1, qubit2)))
[docs] def cz(self, qubit1: int, qubit2: int) -> None: """Apply a CZ gate. See :class:`~graphix.instruction.CZ` for more information. Parameters ---------- qubit1 : int control qubit qubit2 : int target qubit """ assert qubit1 in self.active_qubits assert qubit2 in self.active_qubits assert qubit1 != qubit2 self.instruction.append(Instruction.CZ(targets=(qubit1, qubit2)))
[docs] def h(self, qubit: int) -> None: """Apply a Hadamard gate. See :class:`~graphix.instruction.H` for more information. Parameters ---------- qubit : int target qubit """ assert qubit in self.active_qubits self.instruction.append(Instruction.H(target=qubit))
[docs] def s(self, qubit: int) -> None: """Apply an S gate. See :class:`~graphix.instruction.S` for more information. Parameters ---------- qubit : int target qubit """ assert qubit in self.active_qubits self.instruction.append(Instruction.S(target=qubit))
[docs] def x(self, qubit: int) -> None: """Apply a Pauli X gate. See :class:`~graphix.instruction.X` for more information. Parameters ---------- qubit : int target qubit """ assert qubit in self.active_qubits self.instruction.append(Instruction.X(target=qubit))
[docs] def y(self, qubit: int) -> None: """Apply a Pauli Y gate. See :class:`~graphix.instruction.Y` for more information. Parameters ---------- qubit : int target qubit """ assert qubit in self.active_qubits self.instruction.append(Instruction.Y(target=qubit))
[docs] def z(self, qubit: int) -> None: """Apply a Pauli Z gate. See :class:`~graphix.instruction.Z` for more information. Parameters ---------- qubit : int target qubit """ assert qubit in self.active_qubits self.instruction.append(Instruction.Z(target=qubit))
[docs] def rx(self, qubit: int, angle: ParameterizedAngle) -> None: """Apply an X rotation gate. See :class:`~graphix.instruction.RX` for more information. Parameters ---------- qubit : int target qubit angle : ParameterizedAngle rotation angle in units of π """ assert qubit in self.active_qubits self.instruction.append(Instruction.RX(target=qubit, angle=angle))
[docs] def ry(self, qubit: int, angle: ParameterizedAngle) -> None: """Apply a Y rotation gate. See :class:`~graphix.instruction.RY` for more information. Parameters ---------- qubit : int target qubit angle : ParameterizedAngle angle in units of π """ assert qubit in self.active_qubits self.instruction.append(Instruction.RY(target=qubit, angle=angle))
[docs] def rz(self, qubit: int, angle: ParameterizedAngle) -> None: """Apply a Z rotation gate. See :class:`~graphix.instruction.RZ` for more information. Parameters ---------- qubit : int target qubit angle : ParameterizedAngle rotation angle in units of π """ assert qubit in self.active_qubits self.instruction.append(Instruction.RZ(target=qubit, angle=angle))
[docs] def j(self, qubit: int, angle: ParameterizedAngle) -> None: """Apply a J rotation gate. See :class:`~graphix.instruction.J` for more information. Parameters ---------- qubit : int target qubit angle : ParameterizedAngle rotation angle in units of π """ assert qubit in self.active_qubits self.instruction.append(Instruction.J(target=qubit, angle=angle))
[docs] def r(self, qubit: int, axis: Axis, angle: ParameterizedAngle) -> None: """Apply a rotation gate on the given axis. Parameters ---------- qubit : int target qubit axis : Axis rotation axis angle : ParameterizedAngle rotation angle in units of π """ match axis: case Axis.X: self.rx(qubit, angle) case Axis.Y: self.ry(qubit, angle) case Axis.Z: self.rz(qubit, angle) case _: assert_never(axis)
[docs] def rzz(self, control: int, target: int, angle: ParameterizedAngle) -> None: r"""Apply a ZZ-rotation gate. Equivalent to the sequence CNOT(control, target), Rz(target, angle), CNOT(control, target) and realizes rotation expressed by :math:`e^{-i \frac{\theta}{2} Z_c Z_t}`. See :class:`~graphix.instruction.RZZ` for more information. Parameters ---------- control : int control qubit target : int target qubit angle : ParameterizedAngle rotation angle in units of π """ assert control in self.active_qubits assert target in self.active_qubits self.instruction.append(Instruction.RZZ(control=control, target=target, angle=angle))
[docs] def ccx(self, control1: int, control2: int, target: int) -> None: r"""Apply a CCX (Toffoli) gate. See :class:`~graphix.instruction.CCX` for more information. Parameters ---------- control1 : int first control qubit control2 : int second control qubit target : int target qubit """ assert control1 in self.active_qubits assert control2 in self.active_qubits assert target in self.active_qubits assert control1 != control2 assert control1 != target assert control2 != target self.instruction.append(Instruction.CCX(controls=(control1, control2), target=target))
[docs] def i(self, qubit: int) -> None: """Apply an identity (teleportation) gate. See :class:`~graphix.instruction.I` for more information. Parameters ---------- qubit : int target qubit """ assert qubit in self.active_qubits self.instruction.append(Instruction.I(target=qubit))
[docs] def m(self, qubit: int, axis: Axis) -> None: """Measure a quantum qubit. The measured qubit cannot be used afterwards. See :class:`~graphix.instruction.M` for more information. Parameters ---------- qubit : int target qubit axis : Axis measurement basis """ assert qubit in self.active_qubits self.instruction.append(Instruction.M(target=qubit, axis=axis)) self.active_qubits.remove(qubit)
[docs] def sdg(self, qubit: int) -> None: """Apply an SDG gate. See :class:`~graphix.instruction.SDG` for more information. Parameters ---------- qubit : int target qubit """ assert qubit in self.active_qubits self.instruction.append(Instruction.SDG(target=qubit))
[docs] def t(self, qubit: int) -> None: """Apply a T gate. See :class:`~graphix.instruction.T` for more information. Parameters ---------- qubit : int target qubit """ assert qubit in self.active_qubits self.instruction.append(Instruction.T(target=qubit))
[docs] def tdg(self, qubit: int) -> None: """Apply a TDG gate. See :class:`~graphix.instruction.TDG` for more information. Parameters ---------- qubit : int target qubit """ assert qubit in self.active_qubits self.instruction.append(Instruction.TDG(target=qubit))
[docs] def sx(self, qubit: int) -> None: """Apply an SX gate. See :class:`~graphix.instruction.SX` for more information. Parameters ---------- qubit : int target qubit """ assert qubit in self.active_qubits self.instruction.append(Instruction.SX(target=qubit))
[docs] def sxdg(self, qubit: int) -> None: """Apply an SXDG gate. See :class:`~graphix.instruction.SXDG` for more information. Parameters ---------- qubit : int target qubit """ assert qubit in self.active_qubits self.instruction.append(Instruction.SXDG(target=qubit))
[docs] def cy(self, control: int, target: int) -> None: """Apply a Controlled-Y gate. See :class:`~graphix.instruction.CY` for more information. Parameters ---------- control : int control qubit target : int target qubit """ assert control in self.active_qubits assert target in self.active_qubits assert control != target self.instruction.append(Instruction.CY(control=control, target=target))
[docs] def p(self, qubit: int, angle: ParameterizedAngle) -> None: """Apply a Phase rotation gate. See :class:`~graphix.instruction.P` for more information. Parameters ---------- qubit : int target qubit angle : ParameterizedAngle rotation angle in units of π """ assert qubit in self.active_qubits self.instruction.append(Instruction.P(target=qubit, angle=angle))
[docs] def u(self, qubit: int, theta: ParameterizedAngle, phi: ParameterizedAngle, lambda_: ParameterizedAngle) -> None: """Apply a universal single-qubit gate. See :class:`~graphix.instruction.U` for more information. Parameters ---------- qubit : int target qubit theta : ParameterizedAngle rotation angle in units of π phi : ParameterizedAngle rotation angle in units of π lambda_ : ParameterizedAngle rotation angle in units of π """ assert qubit in self.active_qubits self.instruction.append(Instruction.U(target=qubit, theta=theta, phi=phi, lambda_=lambda_))
[docs] def cj(self, control: int, target: int, angle: ParameterizedAngle) -> None: """Apply a controlled-J rotation gate. See :class:`~graphix.instruction.CJ` for more information. Parameters ---------- control : int control qubit target : int target qubit angle : ParameterizedAngle rotation angle in units of π """ assert control in self.active_qubits assert target in self.active_qubits assert control != target self.instruction.append(Instruction.CJ(control=control, target=target, angle=angle))
[docs] def cp(self, control: int, target: int, angle: ParameterizedAngle) -> None: """Apply a controlled-P rotation gate. See :class:`~graphix.instruction.CP` for more information. Parameters ---------- control : int control qubit target : int target qubit angle : ParameterizedAngle rotation angle in units of π """ assert control in self.active_qubits assert target in self.active_qubits assert control != target self.instruction.append(Instruction.CP(control=control, target=target, angle=angle))
[docs] def crx(self, control: int, target: int, angle: ParameterizedAngle) -> None: """Apply an controlled-X rotation gate. See :class:`~graphix.instruction.CRX` for more information. Parameters ---------- control : int control qubit target : int target qubit angle : ParameterizedAngle rotation angle in units of π """ assert control in self.active_qubits assert target in self.active_qubits assert control != target self.instruction.append(Instruction.CRX(control=control, target=target, angle=angle))
[docs] def cry(self, control: int, target: int, angle: ParameterizedAngle) -> None: """Apply a controlled-Y rotation gate. See :class:`~graphix.instruction.CRY` for more information. Parameters ---------- control : int control qubit target : int target qubit angle : ParameterizedAngle angle in units of π """ assert control in self.active_qubits assert target in self.active_qubits assert control != target self.instruction.append(Instruction.CRY(control=control, target=target, angle=angle))
[docs] def crz(self, control: int, target: int, angle: ParameterizedAngle) -> None: """Apply a controlled-Z rotation gate. See :class:`~graphix.instruction.CRZ` for more information. Parameters ---------- control : int control qubit target : int target qubit angle : ParameterizedAngle rotation angle in units of π """ assert control in self.active_qubits assert target in self.active_qubits assert control != target self.instruction.append(Instruction.CRZ(control=control, target=target, angle=angle))
[docs] def cr(self, control: int, target: int, axis: Axis, angle: ParameterizedAngle) -> None: """Apply a controlled-rotation gate on the given axis. Parameters ---------- control : int control qubit target : int target qubit axis : Axis rotation axis angle : ParameterizedAngle rotation angle in units of π """ match axis: case Axis.X: self.crx(control, target, angle) case Axis.Y: self.cry(control, target, angle) case Axis.Z: self.crz(control, target, angle) case _: assert_never(axis)
[docs] def cu( self, control: int, target: int, theta: ParameterizedAngle, phi: ParameterizedAngle, lambda_: ParameterizedAngle, gamma: ParameterizedAngle, ) -> None: """Apply a controlled-U gate. See :class:`~graphix.instruction.CU` for more information. Parameters ---------- control : int control qubit target : int target qubit theta : ParameterizedAngle rotation angle in units of π phi : ParameterizedAngle rotation angle in units of π lambda_ : ParameterizedAngle rotation angle in units of π gamma : ParameterizedAngle rotation angle in units of π """ assert control in self.active_qubits assert target in self.active_qubits assert control != target self.instruction.append( Instruction.CU(control=control, target=target, theta=theta, phi=phi, lambda_=lambda_, gamma=gamma) )
[docs] def cswap(self, control: int, qubit1: int, qubit2: int) -> None: """Apply a CSWAP gate. See :class:`~graphix.instruction.CSWAP` for more information. Parameters ---------- control : int control qubit qubit1 : int first qubit to be swapped qubit2 : int second qubit to be swapped """ assert control in self.active_qubits assert qubit1 in self.active_qubits assert qubit2 in self.active_qubits assert control != qubit1 assert control != qubit2 assert qubit1 != qubit2 self.instruction.append(Instruction.CSWAP(control=control, targets=(qubit1, qubit2)))
[docs] def gphase(self, angle: ParameterizedAngle) -> None: r"""Apply a global phase. See :class:`~graphix.instruction.GPHASE` for more information. Parameters ---------- angle : ParameterizedAngle rotation angle in units of π """ self.instruction.append(Instruction.GPHASE(angle))
[docs] def transpile_to_causalflow(self) -> TranspiledFlow: """Transpile a circuit via J-∧z decomposition to a causal flow. Parameters ---------- self: the circuit to transpile. Returns ------- the result of the transpilation: a causal flow and classical outputs. """ indices: list[int | None] = list(range(self.width)) n_nodes = self.width measurements: dict[int, BlochMeasurement] = {} classical_outputs: dict[int, command.M] = {} inputs = list(range(n_nodes)) graph: nx.Graph[int] = nx.Graph() graph.add_nodes_from(inputs) x_corrections: dict[int, set[int]] = {} for instr in instructions_to_jcz(self.instruction): match instr.kind: case InstructionKind.M: target = indices[instr.target] if target is None: raise RuntimeError("Ill-formed circuit") classical_outputs[target] = command.M(target, PauliMeasurement(instr.axis)) indices[instr.target] = None continue case InstructionKind.J: target = indices[instr.target] if target is None: raise RuntimeError("Ill-formed circuit") graph.add_edge(target, n_nodes) # Also adds nodes measurements[target] = Measurement.XY(normalize_angle(-instr.angle)) indices[instr.target] = n_nodes x_corrections[target] = {n_nodes} # X correction on ancilla n_nodes += 1 continue case InstructionKind.CZ: t0, t1 = instr.targets i0, i1 = indices[t0], indices[t1] if i0 is None or i1 is None: raise RuntimeError("Ill-formed circuit") # If edge exists, remove it; else, add it if graph.has_edge(i0, i1): graph.remove_edge(i0, i1) else: graph.add_edge(i0, i1) continue case InstructionKind.GPHASE: # Global phase is currently ignored pass case _: assert_never(instr.kind) outputs = [i for i in indices if i is not None] outputs.extend(classical_outputs.keys()) # Necessary for flow-finding step og = OpenGraph( graph=graph, input_nodes=inputs, output_nodes=outputs, measurements=measurements, ) z_corrections: dict[int, set[int]] = {} for node, correctors in x_corrections.items(): z_targets = og.neighbors(correctors) - {node} if z_targets: z_corrections[node] = z_targets partial_order_layers = _corrections_to_partial_order_layers(og, x_corrections, z_corrections) f: CausalFlow[BlochMeasurement] = CausalFlow(og, x_corrections, partial_order_layers) return TranspiledFlow(f, classical_outputs)
[docs] def transpile(self, *, transpile_swaps: bool = True) -> TranspiledPattern: """Transpile a circuit via J-∧z decomposition to a pattern. Parameters ---------- transpile_swaps: bool, optional If ``True`` (the default), SWAP gates are eliminated by switching the qubits. If ``False``, SWAP gates are transpiled into a sequence of three CNOT gates. Returns ------- TranspiledPattern The result of the transpilation: a pattern and classical outputs. """ if not transpile_swaps: return self.transpile_to_causalflow().to_pattern() swap = _transpile_swaps(self, copy=True) result = swap.circuit.transpile_to_causalflow().to_pattern() result.pattern.reorder_output_nodes(swap.swap_output_nodes(result.pattern.output_nodes)) classical_outputs = swap.swap_classical_outputs(result.classical_outputs) return TranspiledPattern(result.pattern, classical_outputs)
@overload def simulate( self, backend: StatevectorBackend | Literal["statevector"] = ..., input_state: Data | None = None, branch_selector: BranchSelector | None = None, rng: Generator | None = None, *, stacklevel: int = 1, ) -> SimulateResult[Statevector]: ... @overload def simulate( self, backend: DensityMatrixBackend | Literal["densitymatrix"], input_state: Data | None = None, branch_selector: BranchSelector | None = None, rng: Generator | None = None, *, stacklevel: int = 1, ) -> SimulateResult[DensityMatrix]: ... @overload def simulate( self, backend: DenseStateBackend[_DenseStateT], input_state: Data | None = None, branch_selector: BranchSelector | None = None, rng: Generator | None = None, *, stacklevel: int = 1, ) -> SimulateResult[_DenseStateT]: ...
[docs] def simulate( self, backend: DenseStateBackend[_DenseStateT] | _DenseStateBackendLiteral = "statevector", input_state: Data | None = None, branch_selector: BranchSelector | None = None, rng: Generator | None = None, *, stacklevel: int = 1, ) -> SimulateResult[_DenseStateT] | SimulateResult[_DenseStateT | Statevector | DensityMatrix]: # `SimulateResult` is not covariant in `_DenseStateT` so `SimulateResult[_DenseStateT]` is not a subtype of `SimulateResult[_DenseStateT | Statevector | DensityMatrix]` r"""Simulate the gate sequence with a backend and input state of choice. By default, this method uses the statevector backend and initializes the register to :math:`|+\rangle^{\otimes n}`. Parameters ---------- input_state : Data backend: :class:`graphix.sim.base_backend.DenseStateBackend[_DenseStateT]`, 'statevector', or 'densitymatrix' Simulator backend to use. Optional, defaults to "statevector". branch_selector: :class:`graphix.branch_selector.BranchSelector` branch selector for measures (default: :class:`RandomBranchSelector`). It cannot be specified if ``backend`` is already instantiated. rng: Generator, optional Random-number generator for measurements. This generator is used only in case of random branch selection (see :class:`RandomBranchSelector`). Returns ------- result : :class:`SimulateResult` output state of the statevector simulation and results of classical measures. """ _backend = _initialize_backend(backend, branch_selector, self.width) if input_state is None: _backend.add_nodes(range(self.width)) else: _backend.add_nodes(range(self.width), input_state) classical_measures: list[Outcome] = [] for i in range(len(self.instruction)): instr = self.instruction[i] def evolve_single(op: Matrix, target: int) -> None: _backend.state.evolve_single(op, _backend.node_index.index(target)) def evolve(op: Matrix, qargs: Iterable[int]) -> None: _backend.state.evolve(op, [_backend.node_index.index(qarg) for qarg in qargs]) match instr.kind: case InstructionKind.CNOT: evolve(Ops.CNOT, [instr.control, instr.target]) case InstructionKind.SWAP: u, v = instr.targets _backend.state.swap((_backend.node_index.index(u), _backend.node_index.index(v))) case InstructionKind.CY: evolve(Ops.CY, [instr.control, instr.target]) case InstructionKind.CZ: u, v = instr.targets _backend.state.entangle((_backend.node_index.index(u), _backend.node_index.index(v))) case InstructionKind.I: pass case InstructionKind.S: evolve_single(Ops.S, instr.target) case InstructionKind.SDG: evolve_single(Ops.SDG, instr.target) case InstructionKind.T: evolve_single(Ops.T, instr.target) case InstructionKind.TDG: evolve_single(Ops.TDG, instr.target) case InstructionKind.SX: evolve_single(Ops.SX, instr.target) case InstructionKind.SXDG: evolve_single(Ops.SXDG, instr.target) case InstructionKind.H: evolve_single(Ops.H, instr.target) case InstructionKind.X: evolve_single(Ops.X, instr.target) case InstructionKind.Y: evolve_single(Ops.Y, instr.target) case InstructionKind.Z: evolve_single(Ops.Z, instr.target) case InstructionKind.P: evolve_single(Ops.p(instr.angle), instr.target) case InstructionKind.RX: evolve_single(Ops.rx(instr.angle), instr.target) case InstructionKind.RY: evolve_single(Ops.ry(instr.angle), instr.target) case InstructionKind.RZ: evolve_single(Ops.rz(instr.angle), instr.target) case InstructionKind.J: evolve_single(Ops.j(instr.angle), instr.target) case InstructionKind.CJ: evolve(Ops.cj(instr.angle), [instr.control, instr.target]) case InstructionKind.U: evolve_single(Ops.u(instr.theta, instr.phi, instr.lambda_), instr.target) case InstructionKind.CU: evolve(Ops.cu(instr.theta, instr.phi, instr.lambda_, instr.gamma), [instr.control, instr.target]) case InstructionKind.CP: evolve(Ops.cp(instr.angle), [instr.control, instr.target]) case InstructionKind.CRX: evolve(Ops.crx(instr.angle), [instr.control, instr.target]) case InstructionKind.CRY: evolve(Ops.cry(instr.angle), [instr.control, instr.target]) case InstructionKind.CRZ: evolve(Ops.crz(instr.angle), [instr.control, instr.target]) case InstructionKind.RZZ: evolve(Ops.rzz(instr.angle), [instr.control, instr.target]) case InstructionKind.CCX: evolve(Ops.CCX, [instr.controls[0], instr.controls[1], instr.target]) case InstructionKind.CSWAP: evolve(Ops.CSWAP, [instr.control, instr.targets[0], instr.targets[1]]) case InstructionKind.M: result = _backend.measure( instr.target, PauliMeasurement(instr.axis), rng=rng, stacklevel=stacklevel + 1 ) classical_measures.append(result) case InstructionKind.GPHASE: # Global phase is currently ignored pass case _: assert_never(instr.kind) return SimulateResult(_backend.state, tuple(classical_measures))
[docs] def visit(self, visitor: InstructionVisitor, *, copy: bool = False) -> Circuit: """Apply ``visitor`` to all instructions in the circuit. Parameters ---------- visitor : InstructionVisitor The visitor specifying the rewriting. copy : bool, optional If ``True``, the current circuit remains unchanged, and a new circuit is returned. The default is ``False``, meaning that changes are performed in place. Returns ------- Self The rewritten circuit. Equal to ``self`` if ``copy`` is ``False``. """ if copy: result = Circuit(self.width) for instr in self.instruction: result.instruction.append(instr.visit(visitor, copy=True)) return result for instr in self.instruction: instr.visit(visitor, copy=False) return self
[docs] def apply_angle(self, f: Callable[[ParameterizedAngle], ParameterizedAngle], *, copy: bool = False) -> Circuit: """Apply ``f`` to all angles that occur in the circuit. Parameters ---------- f : Callable[[ParameterizedAngle], ParameterizedAngle] The function to apply to every angle. copy : bool, optional If ``True``, the current circuit remains unchanged, and a new circuit is returned. The default is ``False``, meaning that changes are performed in place. Returns ------- Self The rewritten circuit. Equal to ``self`` if ``copy`` is ``False``. """ return self.visit(_MapAngleVisitor(f), copy=copy)
[docs] def is_parameterized(self) -> bool: """ Return ``True`` if there is at least one measurement angle that is not just an instance of :class:`SupportsFloat`. A parameterized circuit is a circuit where at least one measurement angle is an expression that is not a number, typically an instance of :class:`sympy.Expr` (but we don't force to choose ``sympy`` here). """ for instr in self.instruction: match instr.kind: case InstructionKind.RZZ | InstructionKind.RX | InstructionKind.RY | InstructionKind.RZ: if not isinstance(instr.angle, SupportsFloat): return True return False
[docs] @override def replace_parameter( self, variable: Parameter, substitute: ExpressionOrSupportsFloat, *, copy: bool = False ) -> Circuit: return self.apply_angle(lambda angle: parameter.with_parameter(angle, variable, substitute), copy=copy)
[docs] @override def replace_parameters( self, assignment: Mapping[Parameter, ExpressionOrSupportsFloat], *, copy: bool = False ) -> Circuit: return self.apply_angle(lambda angle: parameter.with_parameters(angle, assignment), copy=copy)
[docs] def transpile_to_qasm_gates(self) -> Circuit: """Return an equivalent circuit using only the standard OpenQASM gate set.""" new_circuit = Circuit(self.width) for instr in self.instruction: match instr.kind: case InstructionKind.J: new_circuit.add(Instruction.RZ(target=instr.target, angle=instr.angle)) new_circuit.add(Instruction.H(target=instr.target)) case InstructionKind.CJ: new_circuit.extend(decompose_cj(instr)) case InstructionKind.RZZ: new_circuit.extend(decompose_rzz(instr)) case InstructionKind.M: match instr.axis: case Axis.X: new_circuit.h(instr.target) new_circuit.m(instr.target, Axis.Z) case Axis.Y: new_circuit.rx(instr.target, ANGLE_PI / 2) new_circuit.m(instr.target, Axis.Z) case Axis.Z: new_circuit.add(instr) case _: assert_never(instr.axis) case _: new_circuit.add(instr) return new_circuit
[docs] def transpile_rzz(self) -> Circuit: """Return an equivalent circuit where all RZZ gates have been replaced with OpenQASM gates.""" new_circuit = Circuit(self.width) for instr in self.instruction: match instr.kind: case InstructionKind.RZZ: new_circuit.extend(decompose_rzz(instr)) case _: new_circuit.add(instr) return new_circuit
[docs] def decompose_rzz(instr: Instruction.RZZ) -> Iterator[Instruction.CNOT | Instruction.RZ]: """Yield a decomposition of RZZ(α) gate as CNOT(control, target)·Rz(target, α)·CNOT(control, target). Parameters ---------- instr: the RZZ instruction to decompose. Returns ------- the decomposition. """ yield Instruction.CNOT(target=instr.target, control=instr.control) yield Instruction.RZ(instr.target, instr.angle) yield Instruction.CNOT(target=instr.target, control=instr.control)
[docs] def decompose_ccx( instr: Instruction.CCX, ) -> Iterator[Instruction.H | Instruction.CNOT | Instruction.RZ]: """Yield a decomposition of the CCX gate into H, CNOT, T and T-dagger gates. This decomposition of the Toffoli gate can be found in Michael A. Nielsen and Isaac L. Chuang, Quantum Computation and Quantum Information, Cambridge University Press, 2000 (p. 182 in the 10th Anniversary Edition). Parameters ---------- instr: the CCX instruction to decompose. Returns ------- the decomposition. """ c0, c1, t = instr.controls[0], instr.controls[1], instr.target yield Instruction.H(t) yield Instruction.CNOT(control=c1, target=t) yield Instruction.RZ(t, -ANGLE_PI / 4) yield Instruction.CNOT(control=c0, target=t) yield Instruction.RZ(t, ANGLE_PI / 4) yield Instruction.CNOT(control=c1, target=t) yield Instruction.RZ(t, -ANGLE_PI / 4) yield Instruction.CNOT(control=c0, target=t) yield Instruction.RZ(c1, -ANGLE_PI / 4) yield Instruction.RZ(t, ANGLE_PI / 4) yield Instruction.CNOT(control=c0, target=c1) yield Instruction.H(t) yield Instruction.RZ(c1, -ANGLE_PI / 4) yield Instruction.CNOT(control=c0, target=c1) yield Instruction.RZ(c0, ANGLE_PI / 4) yield Instruction.RZ(c1, ANGLE_PI / 2)
[docs] def decompose_cnot(instr: Instruction.CNOT) -> Iterator[Instruction.H | Instruction.CZ]: """Yield a decomposition of the CNOT gate as H·∧z·H. Vincent Danos, Elham Kashefi, Prakash Panangaden, The Measurement Calculus, 2007. Parameters ---------- instr: the CNOT instruction to decompose. Returns ------- the decomposition. """ yield Instruction.H(instr.target) yield Instruction.CZ((instr.control, instr.target)) yield Instruction.H(instr.target)
[docs] def decompose_swap(instr: Instruction.SWAP) -> Iterator[Instruction.CNOT]: """Yield a decomposition of the SWAP gate as CNOT(0, 1)·CNOT(1, 0)·CNOT(0, 1). Michael A. Nielsen and Isaac L. Chuang, Quantum Computation and Quantum Information, Cambridge University Press, 2000 (p. 23 in the 10th Anniversary Edition). Parameters ---------- instr: the SWAP instruction to decompose. Returns ------- the decomposition. """ yield Instruction.CNOT(control=instr.targets[0], target=instr.targets[1]) yield Instruction.CNOT(control=instr.targets[1], target=instr.targets[0]) yield Instruction.CNOT(control=instr.targets[0], target=instr.targets[1])
[docs] def decompose_y(instr: Instruction.Y) -> Iterator[Instruction.X | Instruction.Z | Instruction.GPHASE]: r"""Return a decomposition of the Y gate as :math:`\mathrm e^{\mathrm i \frac \pi 2} X Z`. Parameters ---------- instr: the Y instruction to decompose. Returns ------- the decomposition. """ yield Instruction.Z(instr.target) yield Instruction.X(instr.target) yield Instruction.GPHASE(ANGLE_PI / 2)
[docs] def decompose_rx(instr: Instruction.RX) -> Iterator[Instruction.J | Instruction.GPHASE]: """Yield a J decomposition of the RX gate. The Rx(α) gate is decomposed into J(α)·H (that is to say, J(α)·J(0)). Vincent Danos, Elham Kashefi, Prakash Panangaden, The Measurement Calculus, 2007. Parameters ---------- instr: the RX instruction to decompose. Returns ------- the decomposition. """ yield Instruction.J(instr.target, 0) yield Instruction.J(instr.target, instr.angle) yield Instruction.GPHASE(-instr.angle / 2)
[docs] def decompose_ry(instr: Instruction.RY) -> Iterator[Instruction.J | Instruction.GPHASE]: """Yield a J decomposition of the RY gate. The Ry(α) gate is decomposed into J(0)·J(π/2)·J(α)·J(-π/2). Vincent Danos, Elham Kashefi, Prakash Panangaden, Robust and parsimonious realisations of unitaries in the one-way model, 2004. Parameters ---------- instr: the RY instruction to decompose. Returns ------- the decomposition. """ yield Instruction.J(target=instr.target, angle=-ANGLE_PI / 2) yield Instruction.J(target=instr.target, angle=instr.angle) yield Instruction.J(target=instr.target, angle=ANGLE_PI / 2) yield Instruction.J(target=instr.target, angle=0) yield Instruction.GPHASE(-instr.angle / 2)
[docs] def decompose_rz(instr: Instruction.RZ) -> Iterator[Instruction.J | Instruction.GPHASE]: """Yield a J decomposition of the RZ gate. The Rz(α) gate is decomposed into H·J(α) (that is to say, J(0)·J(α)). Vincent Danos, Elham Kashefi, Prakash Panangaden, The Measurement Calculus, 2007. Parameters ---------- instr: the RZ instruction to decompose. Returns ------- the decomposition. """ yield Instruction.J(target=instr.target, angle=instr.angle) yield Instruction.J(target=instr.target, angle=0) yield Instruction.GPHASE(-instr.angle / 2)
[docs] def decompose_u(instr: Instruction.U) -> Iterator[Instruction.J | Instruction.GPHASE]: r"""Yield a J decomposition of the U gate. The :math:`U(\theta, \phi, \lambda)` gate is decomposed as :math:`e^{-i \theta/2} \cdot H \cdot J(\phi + \pi/2) \cdot J(\theta) \cdot J(\lambda - \pi/2)`, or equivalently, :math:`J(0) \cdot J(\phi + \pi/2) \cdot J(\theta) \cdot J(\lambda - \pi/2)`. Parameters ---------- instr: the U instruction to decompose. Returns ------- the decomposition. """ yield Instruction.J(instr.target, instr.lambda_ - ANGLE_PI / 2) yield Instruction.J(instr.target, instr.theta) yield Instruction.J(instr.target, instr.phi + ANGLE_PI / 2) yield Instruction.J(instr.target, 0) yield Instruction.GPHASE(-instr.theta / 2)
[docs] def decompose_cu(instr: Instruction.CU) -> Iterator[Instruction.CJ | Instruction.P]: """Yield a J decomposition of the U gate. The U(θ, φ, λ) gate is decomposed into H·J(φ + 𝜋/2)·J(θ)·J(λ - 𝜋/2) (that is to say, J(0)·J(φ + 𝜋/2)·J(θ)·J(λ - 𝜋/2)). Parameters ---------- instr: the U instruction to decompose. Returns ------- the decomposition. """ yield Instruction.CJ(control=instr.control, target=instr.target, angle=instr.lambda_ - ANGLE_PI / 2) yield Instruction.CJ(control=instr.control, target=instr.target, angle=instr.theta) yield Instruction.CJ(control=instr.control, target=instr.target, angle=instr.phi + ANGLE_PI / 2) yield Instruction.CJ(control=instr.control, target=instr.target, angle=0) yield Instruction.P(target=instr.control, angle=instr.gamma - instr.theta / 2)
[docs] def insert_control( control: int, instrs: Iterable[ Instruction.GPHASE | Instruction.X | Instruction.Z | Instruction.J | Instruction.CNOT | Instruction.RZ ], ) -> Iterator[Instruction.CNOT | Instruction.CZ | Instruction.CJ | Instruction.CCX | Instruction.CRZ | Instruction.P]: """Yield a controlled gate sequence from a gate sequence. Parameters ---------- control: int The control qubit. instrs: Iterable[Instruction.GPHASE | Instruction.X | Instruction.Z | Instruction.J | Instruction.CNOT | Instruction.RZ] The gate sequence. Yields ------ InstructionType The controlled gate sequence. """ for instr in instrs: match instr.kind: case InstructionKind.X: yield Instruction.CNOT(control=control, target=instr.target) case InstructionKind.Z: yield Instruction.CZ((control, instr.target)) case InstructionKind.J: yield Instruction.CJ(control=control, target=instr.target, angle=instr.angle) case InstructionKind.CNOT: yield Instruction.CCX(target=instr.target, controls=(control, instr.control)) case InstructionKind.RZ: yield Instruction.CRZ(control=control, target=instr.target, angle=instr.angle) case InstructionKind.GPHASE: yield Instruction.P(target=control, angle=instr.angle) case _: assert_never(instr.kind)
[docs] def decompose_cj(instr: Instruction.CJ) -> Iterator[Instruction.RZ | Instruction.CNOT | Instruction.RY | Instruction.P]: """Yield a decomposed gate sequence of the CJ gate. See :class:`~graphix.instruction.CJ` for more information. """ delta = (instr.angle + ANGLE_PI) / 2 yield Instruction.RZ(target=instr.target, angle=delta) yield Instruction.CNOT(control=instr.control, target=instr.target) yield Instruction.RZ(target=instr.target, angle=-delta) yield Instruction.RY(target=instr.target, angle=-ANGLE_PI / 4) yield Instruction.CNOT(control=instr.control, target=instr.target) yield Instruction.RY(target=instr.target, angle=ANGLE_PI / 4) yield Instruction.P(target=instr.control, angle=delta)
[docs] def decompose_p(instr: Instruction.P) -> Iterator[Instruction.RZ | Instruction.GPHASE]: """Yield a decomposed gate sequence of the P gate. See :class:`~graphix.instruction.P` for more information. """ yield Instruction.RZ(instr.target, instr.angle) yield Instruction.GPHASE(instr.angle / 2)
[docs] def instructions_to_jcz( instrs: Iterable[InstructionType], ) -> Iterator[Instruction.J | Instruction.CZ | Instruction.M | Instruction.GPHASE]: """Yield a J-∧z decomposition of the instruction. Parameters ---------- instr: the instruction to decompose. Returns ------- the decomposition. """ for instr in instrs: match instr.kind: case InstructionKind.J | InstructionKind.CZ | InstructionKind.M: yield instr case InstructionKind.I: return case InstructionKind.H: yield Instruction.J(instr.target, 0) case InstructionKind.S: yield from decompose_rz(Instruction.RZ(instr.target, ANGLE_PI / 2)) case InstructionKind.SDG: yield from decompose_rz(Instruction.RZ(instr.target, -ANGLE_PI / 2)) case InstructionKind.T: yield from decompose_rz(Instruction.RZ(instr.target, ANGLE_PI / 4)) case InstructionKind.TDG: yield from decompose_rz(Instruction.RZ(instr.target, -ANGLE_PI / 4)) case InstructionKind.SX: yield from decompose_rx(Instruction.RX(instr.target, ANGLE_PI / 2)) case InstructionKind.SXDG: yield from decompose_rx(Instruction.RX(instr.target, -ANGLE_PI / 2)) case InstructionKind.X: yield from decompose_rx(Instruction.RX(instr.target, ANGLE_PI)) case InstructionKind.Y: yield from instructions_to_jcz(decompose_y(instr)) case InstructionKind.Z: yield from decompose_rz(Instruction.RZ(instr.target, ANGLE_PI)) case InstructionKind.RX: yield from decompose_rx(instr) case InstructionKind.RY: yield from decompose_ry(instr) case InstructionKind.RZ: yield from decompose_rz(instr) case InstructionKind.P: yield from instructions_to_jcz(decompose_p(instr)) case InstructionKind.U: yield from decompose_u(instr) case InstructionKind.CCX: yield from instructions_to_jcz(decompose_ccx(instr)) case InstructionKind.RZZ: yield from instructions_to_jcz(decompose_rzz(instr)) case InstructionKind.CNOT: yield from instructions_to_jcz(decompose_cnot(instr)) case InstructionKind.SWAP: yield from instructions_to_jcz(decompose_swap(instr)) case InstructionKind.CY: yield from instructions_to_jcz(insert_control(instr.control, decompose_y(Instruction.Y(instr.target)))) case InstructionKind.CJ: yield from instructions_to_jcz(decompose_cj(instr)) case InstructionKind.CP: yield from instructions_to_jcz( insert_control(instr.control, decompose_p(Instruction.P(instr.target, instr.angle))) ) case InstructionKind.CRX: yield from instructions_to_jcz( insert_control(instr.control, decompose_rx(Instruction.RX(instr.target, instr.angle))) ) case InstructionKind.CRY: yield from instructions_to_jcz( insert_control(instr.control, decompose_ry(Instruction.RY(instr.target, instr.angle))) ) case InstructionKind.CRZ: yield from instructions_to_jcz( insert_control(instr.control, decompose_rz(Instruction.RZ(instr.target, instr.angle))) ) case InstructionKind.CU: yield from instructions_to_jcz(decompose_cu(instr)) case InstructionKind.CSWAP: yield from instructions_to_jcz( insert_control(instr.control, decompose_swap(Instruction.SWAP(instr.targets))) ) case InstructionKind.GPHASE: yield instr case _: assert_never(instr.kind)
[docs] def normalize_angle(angle: ParameterizedAngle) -> ParameterizedAngle: r"""Return an equivalent angle in range :math:`[0, 2 \cdot \pi)` if ``angle`` is instantiated. Parameters ---------- angle: ParameterizedAngle An angle. Returns ------- ParameterizedAngle An equivalent angle in range :math:`[0, 2 \cdot \pi)` if ``angle`` is instantiated. If ``angle`` is parameterized, ``angle`` is returned unchanged. """ if isinstance(angle, float): return angle % (2 * ANGLE_PI) return angle
[docs] @dataclass(frozen=True) class TranspileSwapsResult: """The result returned by :func:`transpile_swaps`.""" circuit: Circuit """Circuit without SWAP gates.""" outputs: tuple[OutputIndex, ...] """ Tuple which has the same width as the circuit and which for every qubit of the original circuit provides the index of the corresponding qubit in the output of the swapped circuit (either measured or not). """
[docs] def extract_outputs(self, kind: OutputKind) -> tuple[int, ...]: """Return the sequence of outputs of the given kind.""" return tuple(output.index for output in self.outputs if output.kind == kind)
[docs] def extract_output_node_indices(self) -> tuple[int, ...]: """Return for each output node, sorted in the order of the original circuit, the index of the corresponding output node in the order of the swapped circuit. This method returns a permutation of ``range(number_of_output_qubits)``. """ qubit_indices = self.extract_outputs(OutputKind.Qubit) rank = {q: i for i, q in enumerate(sorted(qubit_indices))} return tuple(rank[q] for q in qubit_indices)
[docs] def swap_output_nodes(self, output_nodes: Sequence[Node]) -> tuple[Node, ...]: """Reorder the output nodes of a pattern obtained from a swapped circuit to restore the qubit ordering of the original circuit.""" return tuple(output_nodes[index] for index in self.extract_output_node_indices())
[docs] def swap_classical_outputs(self, classical_outputs: Sequence[Node]) -> tuple[int, ...]: """Reorder the classical outpus of a pattern obtained from a swapped circuit to restore the output ordering of the original circuit.""" return tuple(classical_outputs[index] for index in self.extract_outputs(OutputKind.Bit))
[docs] class OutputKind(Enum): """Specify whether a qubit is measured or not.""" Qubit = enum.auto() Bit = enum.auto()
[docs] @dataclass(frozen=True) class OutputIndex: """Index of a swapped qubit. If the qubit is measured, ``kind`` equals to `OutputKind.Bit` and ``index`` is the index of the measurement. If the qubit is not measured, ``kind`` equals to `OutputKind.qubit` and ``index`` is the index of the qubit in the swapped circuit. """ kind: OutputKind index: int
class _TranspileSwapVisitor(InstructionVisitor): outputs: list[OutputIndex] def __init__(self, width: int) -> None: self.outputs = [OutputIndex(OutputKind.Qubit, index) for index in range(width)] @override def visit_qubit(self, qubit: int) -> int: target = self.outputs[qubit] if target.kind == OutputKind.Bit: raise RuntimeError(f"Qubit {qubit} has already been measured.") return target.index
[docs] def transpile_swaps(circuit: Circuit, *, copy: bool = False) -> TranspileSwapsResult: """Return a new circuit equivalent to the original one but without SWAP gates. Parameters ---------- circuit : Circuit The original circuit copy : bool, optional If ``True``, the current pattern remains unchanged, and a new pattern is returned. The default is ``False``, meaning that changes are performed in place. Returns ------- TranspileSwapsResult The field ``circuit`` contains an equivalent circuit without SWAP gates. Equal to ``self`` if ``copy`` is ``False``. The field ``outputs`` contains a tuple which has the same width as the circuit. For every qubit of the original circuit, either the qubit is not measured, and ``outputs`` provides the index of the corresponding qubit in the output of the returned circuit; or the qubit has been measured, and ``outputs`` provides the index of the measurement. """ new_circuit = Circuit(circuit.width) visitor = _TranspileSwapVisitor(circuit.width) measurement_index = 0 for instr in circuit.instruction: if instr.kind == InstructionKind.SWAP: u, v = instr.targets # We apply the visitor to check that the qubits have not been measured. visitor.visit_qubit(u) visitor.visit_qubit(v) visitor.outputs[u], visitor.outputs[v] = visitor.outputs[v], visitor.outputs[u] elif instr.kind == InstructionKind.M: old_target = instr.target new_circuit.add(instr.visit(visitor, copy=copy)) visitor.outputs[old_target] = OutputIndex(OutputKind.Bit, measurement_index) measurement_index += 1 else: new_circuit.add(instr.visit(visitor, copy=copy)) if not copy: circuit.instruction = new_circuit.instruction new_circuit = circuit return TranspileSwapsResult(new_circuit, tuple(visitor.outputs))
# Alias `_transpile_swaps` to call the function in `Circuit.transpile` # method where `transpile_swaps` is shadowed by the keyword parameter. _transpile_swaps = transpile_swaps @overload def _initialize_backend( backend: StatevectorBackend | Literal["statevector"], branch_selector: BranchSelector | None, width: int, ) -> StatevectorBackend: ... @overload def _initialize_backend( backend: DensityMatrixBackend | Literal["densitymatrix"], branch_selector: BranchSelector | None, width: int, ) -> DensityMatrixBackend: ... @overload def _initialize_backend( backend: DenseStateBackend[_DenseStateT], branch_selector: BranchSelector | None, width: int, ) -> DenseStateBackend[_DenseStateT]: ... def _initialize_backend( backend: DenseStateBackend[_DenseStateT] | _DenseStateBackendLiteral, branch_selector: BranchSelector | None, width: int, ) -> _BuiltinDenseStateBackend | DenseStateBackend[_DenseStateT]: """Initialize backend for circuit simulation. Parameters ---------- backend: :class:`graphix.sim.base_backend.DenseStateBackend[_DenseStateT]`, 'statevector', or 'densitymatrix' Simulation backend branch_selector: :class:`BranchSelector` Branch selector used for measurements. Can only be specified if ``backend`` is not an already instantiated :class:`Backend` object. If ``None``, it defaults to :class:`RandomBranchSelector`. width : int Number of qubits in circuit. It is required to initialize the :class:`StatevectorBackend` with the appropriate capacity. Returns ------- :class:`DenseStateBackend` matching the appropriate backend """ if isinstance(backend, DenseStateBackend): if branch_selector is not None: raise ValueError("`branch_selector` cannot be specified if `backend` is already instantiated.") return backend if branch_selector is None: branch_selector = RandomBranchSelector() match backend: case "statevector": return StatevectorBackend.with_capacity(width, branch_selector=branch_selector) case "densitymatrix": return DensityMatrixBackend(branch_selector=branch_selector) case _: raise ValueError(f"Unknown backend {backend}.")