Source code for graphix.transpiler

"""Gate-to-MBQC transpiler.

accepts desired gate operations and transpile into MBQC measurement patterns.

"""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, SupportsFloat

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

from graphix import command, instruction, parameter
from graphix.branch_selector import BranchSelector, RandomBranchSelector
from graphix.command import E, M, N, X, Z
from graphix.fundamentals import ANGLE_PI, Axis
from graphix.instruction import Instruction, InstructionKind, InstructionVisitor, InstructionWithoutRZZ
from graphix.measurements import Measurement, PauliMeasurement
from graphix.ops import Ops
from graphix.pattern import Pattern
from graphix.sim.statevec import Statevec, StatevectorBackend

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

    from numpy.random import Generator

    from graphix.command import Command
    from graphix.fundamentals import ParameterizedAngle
    from graphix.parameter import ExpressionOrFloat, Parameter
    from graphix.sim import Data
    from graphix.sim.base_backend import Matrix


[docs] @dataclass class TranspileResult: """ The result of a transpilation. pattern : :class:`graphix.pattern.Pattern` object classical_outputs : tuple[int,...], index of nodes measured with *M* gates """ pattern: Pattern classical_outputs: tuple[int, ...]
[docs] @dataclass class SimulateResult: """ The result of a simulation. statevec : :class:`graphix.sim.statevec.Statevec` object classical_measures : tuple[int,...], classical measures """ statevec: Statevec classical_measures: tuple[int, ...]
def _check_target(out: Sequence[int | None], index: int) -> int: target = out[index] if target is None: msg = f"Qubit {index} has already been measured." raise ValueError(msg) return target @dataclass class _MapAngleVisitor(InstructionVisitor): f: Callable[[ParameterizedAngle], ParameterizedAngle] @override def visit_angle(self, angle: ParameterizedAngle) -> ParameterizedAngle: return self.f(angle)
[docs] class Circuit: """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[Instruction]
[docs] def __init__(self, width: int, instr: Iterable[Instruction] | None = None) -> None: """ Construct a circuit. Parameters ---------- width : int number of logical qubits for the gate network instr : list[instruction.Instruction] | 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)
def add(self, instr: Instruction) -> 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 _: assert_never(instr.kind) def extend(self, instrs: Iterable[Instruction]) -> 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. 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))
def swap(self, qubit1: int, qubit2: int) -> None: """Apply a SWAP gate. 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))) def cz(self, qubit1: int, qubit2: int) -> None: """Apply a CNOT gate. 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. 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. 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. 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. 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. 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. 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. 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. 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))
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) 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}`. 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. Prameters --------- 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))
def i(self, qubit: int) -> None: """Apply an identity (teleportation) gate. 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. 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 transpile(self) -> TranspileResult: """Transpile the circuit to a pattern. Returns ------- result : :class:`TranspileResult` object """ n_node = self.width out: list[int | None] = list(range(self.width)) pattern = Pattern(input_nodes=list(range(self.width))) classical_outputs = [] for instr in _transpile_rzz(self.instruction): match instr.kind: case instruction.InstructionKind.CZ: target0 = _check_target(out, instr.targets[0]) target1 = _check_target(out, instr.targets[1]) seq = self._cz_command(target0, target1) pattern.extend(seq) case instruction.InstructionKind.CNOT: ancilla = [n_node, n_node + 1] control = _check_target(out, instr.control) target = _check_target(out, instr.target) out[instr.control], out[instr.target], seq = self._cnot_command(control, target, ancilla) pattern.extend(seq) n_node += 2 case instruction.InstructionKind.SWAP: target0 = _check_target(out, instr.targets[0]) target1 = _check_target(out, instr.targets[1]) out[instr.targets[0]], out[instr.targets[1]] = ( target1, target0, ) case instruction.InstructionKind.I: pass case instruction.InstructionKind.H: single_ancilla = n_node target = _check_target(out, instr.target) out[instr.target], seq = self._h_command(target, single_ancilla) pattern.extend(seq) n_node += 1 case instruction.InstructionKind.S: ancilla = [n_node, n_node + 1] target = _check_target(out, instr.target) out[instr.target], seq = self._s_command(target, ancilla) pattern.extend(seq) n_node += 2 case instruction.InstructionKind.X: ancilla = [n_node, n_node + 1] target = _check_target(out, instr.target) out[instr.target], seq = self._x_command(target, ancilla) pattern.extend(seq) n_node += 2 case instruction.InstructionKind.Y: ancilla = [n_node, n_node + 1, n_node + 2, n_node + 3] target = _check_target(out, instr.target) out[instr.target], seq = self._y_command(target, ancilla) pattern.extend(seq) n_node += 4 case instruction.InstructionKind.Z: ancilla = [n_node, n_node + 1] target = _check_target(out, instr.target) out[instr.target], seq = self._z_command(target, ancilla) pattern.extend(seq) n_node += 2 case instruction.InstructionKind.RX: ancilla = [n_node, n_node + 1] target = _check_target(out, instr.target) out[instr.target], seq = self._rx_command(target, ancilla, instr.angle) pattern.extend(seq) n_node += 2 case instruction.InstructionKind.RY: ancilla = [n_node, n_node + 1, n_node + 2, n_node + 3] target = _check_target(out, instr.target) out[instr.target], seq = self._ry_command(target, ancilla, instr.angle) pattern.extend(seq) n_node += 4 case instruction.InstructionKind.RZ: ancilla = [n_node, n_node + 1] target = _check_target(out, instr.target) out[instr.target], seq = self._rz_command(target, ancilla, instr.angle) pattern.extend(seq) n_node += 2 case instruction.InstructionKind.CCX: ancilla = [n_node + i for i in range(18)] control0 = _check_target(out, instr.controls[0]) control1 = _check_target(out, instr.controls[1]) target = _check_target(out, instr.target) ( out[instr.controls[0]], out[instr.controls[1]], out[instr.target], seq, ) = self._ccx_command( control0, control1, target, ancilla, ) pattern.extend(seq) n_node += 18 case instruction.InstructionKind.M: target = _check_target(out, instr.target) seq = self._m_command(target, instr.axis) pattern.extend(seq) classical_outputs.append(target) out[instr.target] = None case _: assert_never(instr.kind) output_nodes = [node for node in out if node is not None] pattern.reorder_output_nodes(output_nodes) return TranspileResult(pattern, tuple(classical_outputs))
@classmethod def _cnot_command( cls, control_node: int, target_node: int, ancilla: Sequence[int] ) -> tuple[int, int, list[command.Command]]: """MBQC commands for CNOT gate. Parameters ---------- control_node : int control node on graph target : int target node on graph ancilla : list of two ints ancilla node indices to be added to graph Returns ------- control_out : int control node on graph after the gate target_out : int target node on graph after the gate commands : list list of MBQC commands """ assert len(ancilla) == 2 seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] seq.extend( ( E(nodes=(target_node, ancilla[0])), E(nodes=(control_node, ancilla[0])), E(nodes=(ancilla[0], ancilla[1])), M(node=target_node), M(node=ancilla[0], s_domain={target_node}), X(node=ancilla[1], domain={ancilla[0]}), Z(node=ancilla[1], domain={target_node}), Z(node=control_node, domain={target_node}), ) ) return control_node, ancilla[1], seq @classmethod def _cz_command(cls, target_1: int, target_2: int) -> list[Command]: """MBQC commands for CZ gate. Parameters ---------- target_1 : int target node on graph target_2 : int other target node on graph Returns ------- commands : list list of MBQC commands """ return [E(nodes=(target_1, target_2))] @classmethod def _m_command(cls, input_node: int, axis: Axis) -> list[Command]: """MBQC commands for measuring qubit. Parameters ---------- input_node : int target node on graph axis : Axis measurement basis Returns ------- commands : list list of MBQC commands """ # `measurement.angle` and `M.angle` are both expressed in units of π. return [M(input_node, PauliMeasurement(axis))] @classmethod def _h_command(cls, input_node: int, ancilla: int) -> tuple[int, list[Command]]: """MBQC commands for Hadamard gate. Parameters ---------- input_node : int target node on graph ancilla : int ancilla node index to be added Returns ------- out_node : int control node on graph after the gate commands : list list of MBQC commands """ seq: list[Command] = [N(node=ancilla)] seq.extend((E(nodes=(input_node, ancilla)), M(node=input_node), X(node=ancilla, domain={input_node}))) return ancilla, seq @classmethod def _s_command(cls, input_node: int, ancilla: Sequence[int]) -> tuple[int, list[command.Command]]: """MBQC commands for S gate. Parameters ---------- input_node : int input node index ancilla : list of two ints ancilla node indices to be added to graph Returns ------- out_node : int control node on graph after the gate commands : list list of MBQC commands """ assert len(ancilla) == 2 seq: list[Command] = [N(node=ancilla[0]), command.N(node=ancilla[1])] seq.extend( ( E(nodes=(input_node, ancilla[0])), E(nodes=(ancilla[0], ancilla[1])), M(input_node, -Measurement.Y), M(node=ancilla[0], s_domain={input_node}), X(node=ancilla[1], domain={ancilla[0]}), Z(node=ancilla[1], domain={input_node}), ) ) return ancilla[1], seq @classmethod def _x_command(cls, input_node: int, ancilla: Sequence[int]) -> tuple[int, list[command.Command]]: """MBQC commands for Pauli X gate. Parameters ---------- input_node : int input node index ancilla : list of two ints ancilla node indices to be added to graph Returns ------- out_node : int control node on graph after the gate commands : list list of MBQC commands """ assert len(ancilla) == 2 seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] seq.extend( ( E(nodes=(input_node, ancilla[0])), E(nodes=(ancilla[0], ancilla[1])), M(node=input_node), M(ancilla[0], -Measurement.X, s_domain={input_node}), X(node=ancilla[1], domain={ancilla[0]}), Z(node=ancilla[1], domain={input_node}), ) ) return ancilla[1], seq @classmethod def _y_command(cls, input_node: int, ancilla: Sequence[int]) -> tuple[int, list[command.Command]]: """MBQC commands for Pauli Y gate. Parameters ---------- input_node : int input node index ancilla : list of four ints ancilla node indices to be added to graph Returns ------- out_node : int control node on graph after the gate commands : list list of MBQC commands """ assert len(ancilla) == 4 seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] seq.extend([N(node=ancilla[2]), N(node=ancilla[3])]) seq.extend( ( E(nodes=(input_node, ancilla[0])), E(nodes=(ancilla[0], ancilla[1])), E(nodes=(ancilla[1], ancilla[2])), E(nodes=(ancilla[2], ancilla[3])), M(input_node, Measurement.Y), M(ancilla[0], -Measurement.X, s_domain={input_node}), M(ancilla[1], -Measurement.Y, s_domain={ancilla[0]}, t_domain={input_node}), M(node=ancilla[2], s_domain={ancilla[1]}, t_domain={ancilla[0]}), X(node=ancilla[3], domain={ancilla[2]}), Z(node=ancilla[3], domain={ancilla[1]}), ) ) return ancilla[3], seq @classmethod def _z_command(cls, input_node: int, ancilla: Sequence[int]) -> tuple[int, list[command.Command]]: """MBQC commands for Pauli Z gate. Parameters ---------- input_node : int input node index ancilla : list of two ints ancilla node indices to be added to graph Returns ------- out_node : int control node on graph after the gate commands : list list of MBQC commands """ assert len(ancilla) == 2 seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] seq.extend( ( E(nodes=(input_node, ancilla[0])), E(nodes=(ancilla[0], ancilla[1])), M(input_node, -Measurement.X), M(node=ancilla[0], s_domain={input_node}), X(node=ancilla[1], domain={ancilla[0]}), Z(node=ancilla[1], domain={input_node}), ) ) return ancilla[1], seq @classmethod def _rx_command( cls, input_node: int, ancilla: Sequence[int], angle: ParameterizedAngle ) -> tuple[int, list[command.Command]]: """MBQC commands for X rotation gate. Parameters ---------- input_node : int input node index ancilla : list of two ints ancilla node indices to be added to graph angle : ParameterizedAngle measurement angle in units of π Returns ------- out_node : int control node on graph after the gate commands : list list of MBQC commands """ assert len(ancilla) == 2 seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] seq.extend( ( E(nodes=(input_node, ancilla[0])), E(nodes=(ancilla[0], ancilla[1])), M(node=input_node), M(ancilla[0], Measurement.XY(-angle), s_domain={input_node}), X(node=ancilla[1], domain={ancilla[0]}), Z(node=ancilla[1], domain={input_node}), ) ) return ancilla[1], seq @classmethod def _ry_command( cls, input_node: int, ancilla: Sequence[int], angle: ParameterizedAngle ) -> tuple[int, list[command.Command]]: """MBQC commands for Y rotation gate. Parameters ---------- input_node : int input node index ancilla : list of four ints ancilla node indices to be added to graph angle : ParameterizedAngle rotation angle in units of π Returns ------- out_node : int control node on graph after the gate commands : list list of MBQC commands """ assert len(ancilla) == 4 seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] seq.extend([N(node=ancilla[2]), N(node=ancilla[3])]) seq.extend( ( E(nodes=(input_node, ancilla[0])), E(nodes=(ancilla[0], ancilla[1])), E(nodes=(ancilla[1], ancilla[2])), E(nodes=(ancilla[2], ancilla[3])), M(input_node, Measurement.Y), M(ancilla[0], Measurement.XY(-angle), s_domain={input_node}), M(ancilla[1], -Measurement.Y, s_domain={ancilla[0]}, t_domain={input_node}), M(node=ancilla[2], s_domain={ancilla[1]}, t_domain={ancilla[0]}), X(node=ancilla[3], domain={ancilla[2]}), Z(node=ancilla[3], domain={ancilla[1]}), ) ) return ancilla[3], seq @classmethod def _rz_command( cls, input_node: int, ancilla: Sequence[int], angle: ParameterizedAngle ) -> tuple[int, list[command.Command]]: """MBQC commands for Z rotation gate. Parameters ---------- input_node : int input node index ancilla : list of two ints ancilla node indices to be added to graph angle : ParameterizedAngle measurement angle in units of π Returns ------- out_node : int node on graph after the gate commands : list list of MBQC commands """ assert len(ancilla) == 2 seq: list[Command] = [N(node=ancilla[0]), N(node=ancilla[1])] # assign new qubit labels seq.extend( ( E(nodes=(input_node, ancilla[0])), E(nodes=(ancilla[0], ancilla[1])), M(input_node, Measurement.XY(-angle)), M(node=ancilla[0], s_domain={input_node}), X(node=ancilla[1], domain={ancilla[0]}), Z(node=ancilla[1], domain={input_node}), ) ) return ancilla[1], seq @classmethod def _ccx_command( cls, control_node1: int, control_node2: int, target_node: int, ancilla: Sequence[int], ) -> tuple[int, int, int, list[command.Command]]: """MBQC commands for CCX gate. Parameters ---------- control_node1 : int first control node on graph control_node2 : int second control node on graph target_node : int target node on graph ancilla : list of int ancilla node indices to be added to graph Returns ------- control_out1 : int first control node on graph after the gate control_out2 : int second control node on graph after the gate target_out : int target node on graph after the gate commands : list list of MBQC commands """ assert len(ancilla) == 18 seq: list[Command] = [N(node=ancilla[i]) for i in range(18)] # assign new qubit labels seq.extend( ( E(nodes=(target_node, ancilla[0])), E(nodes=(ancilla[0], ancilla[1])), E(nodes=(ancilla[1], ancilla[2])), E(nodes=(ancilla[1], control_node2)), E(nodes=(control_node1, ancilla[14])), E(nodes=(ancilla[2], ancilla[3])), E(nodes=(ancilla[14], ancilla[4])), E(nodes=(ancilla[3], ancilla[5])), E(nodes=(ancilla[3], ancilla[4])), E(nodes=(ancilla[5], ancilla[6])), E(nodes=(control_node2, ancilla[6])), E(nodes=(control_node2, ancilla[9])), E(nodes=(ancilla[6], ancilla[7])), E(nodes=(ancilla[9], ancilla[4])), E(nodes=(ancilla[9], ancilla[10])), E(nodes=(ancilla[7], ancilla[8])), E(nodes=(ancilla[10], ancilla[11])), E(nodes=(ancilla[4], ancilla[8])), E(nodes=(ancilla[4], ancilla[11])), E(nodes=(ancilla[4], ancilla[16])), E(nodes=(ancilla[8], ancilla[12])), E(nodes=(ancilla[11], ancilla[15])), E(nodes=(ancilla[12], ancilla[13])), E(nodes=(ancilla[16], ancilla[17])), M(node=target_node), M(node=ancilla[0], s_domain={target_node}), M(node=ancilla[1], s_domain={ancilla[0]}, t_domain={target_node}), M(node=control_node1), M(ancilla[2], Measurement.XY(-7 * ANGLE_PI / 4), s_domain={ancilla[1]}, t_domain={ancilla[0]}), M(node=ancilla[14], s_domain={control_node1}), M(node=ancilla[3], s_domain={ancilla[2]}, t_domain={ancilla[1], ancilla[14]}), M(ancilla[5], Measurement.XY(-ANGLE_PI / 4), s_domain={ancilla[3]}, t_domain={ancilla[2]}), M(control_node2, Measurement.XY(-ANGLE_PI / 4), t_domain={ancilla[5], ancilla[0]}), M(node=ancilla[6], s_domain={ancilla[5]}, t_domain={ancilla[3]}), M(node=ancilla[9], s_domain={control_node2}, t_domain={ancilla[14]}), M(ancilla[7], Measurement.XY(-7 * ANGLE_PI / 4), s_domain={ancilla[6]}, t_domain={ancilla[5]}), M(ancilla[10], Measurement.XY(-7 * ANGLE_PI / 4), s_domain={ancilla[9]}, t_domain={control_node2}), M( ancilla[4], Measurement.XY(-ANGLE_PI / 4), s_domain={ancilla[14]}, t_domain={control_node1, control_node2, ancilla[2], ancilla[7], ancilla[10]}, ), M(node=ancilla[8], s_domain={ancilla[7]}, t_domain={ancilla[14], ancilla[6]}), M(node=ancilla[11], s_domain={ancilla[10]}, t_domain={ancilla[9], ancilla[14]}), M(ancilla[12], Measurement.XY(-ANGLE_PI / 4), s_domain={ancilla[8]}, t_domain={ancilla[7]}), M( node=ancilla[16], s_domain={ancilla[4]}, t_domain={ancilla[14]}, ), X(node=ancilla[17], domain={ancilla[16]}), X(node=ancilla[15], domain={ancilla[11]}), X(node=ancilla[13], domain={ancilla[12]}), Z(node=ancilla[17], domain={ancilla[4]}), Z(node=ancilla[15], domain={ancilla[10]}), Z(node=ancilla[13], domain={ancilla[8]}), ) ) return ancilla[17], ancilla[15], ancilla[13], seq
[docs] def simulate_statevector( self, input_state: Data | None = None, branch_selector: BranchSelector | None = None, rng: Generator | None = None, *, stacklevel: int = 1, ) -> SimulateResult: """Run statevector simulation of the gate sequence. Parameters ---------- input_state : Data branch_selector: :class:`graphix.branch_selector.BranchSelector` branch selector for measures (default: :class:`RandomBranchSelector`). 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. """ if branch_selector is None: branch_selector = RandomBranchSelector() backend = StatevectorBackend(branch_selector=branch_selector) if input_state is None: backend.add_nodes(range(self.width)) else: backend.add_nodes(range(self.width), input_state) classical_measures = [] 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 instruction.InstructionKind.CNOT: backend.state.cnot( (backend.node_index.index(instr.control), backend.node_index.index(instr.target)) ) case instruction.InstructionKind.SWAP: u, v = instr.targets backend.state.swap((backend.node_index.index(u), backend.node_index.index(v))) case instruction.InstructionKind.CZ: u, v = instr.targets backend.state.entangle((backend.node_index.index(u), backend.node_index.index(v))) case instruction.InstructionKind.I: pass case instruction.InstructionKind.S: evolve_single(Ops.S, instr.target) case instruction.InstructionKind.H: evolve_single(Ops.H, instr.target) case instruction.InstructionKind.X: evolve_single(Ops.X, instr.target) case instruction.InstructionKind.Y: evolve_single(Ops.Y, instr.target) case instruction.InstructionKind.Z: evolve_single(Ops.Z, instr.target) case instruction.InstructionKind.RX: evolve_single(Ops.rx(instr.angle), instr.target) case instruction.InstructionKind.RY: evolve_single(Ops.ry(instr.angle), instr.target) case instruction.InstructionKind.RZ: evolve_single(Ops.rz(instr.angle), instr.target) case instruction.InstructionKind.RZZ: evolve(Ops.rzz(instr.angle), [instr.control, instr.target]) case instruction.InstructionKind.CCX: evolve(Ops.CCX, [instr.controls[0], instr.controls[1], instr.target]) case instruction.InstructionKind.M: result = backend.measure( instr.target, PauliMeasurement(instr.axis), rng=rng, stacklevel=stacklevel + 1 ) classical_measures.append(result) case _: raise ValueError(f"Unknown instruction: {instr}") return SimulateResult(backend.state, tuple(classical_measures))
def visit(self, visitor: InstructionVisitor) -> Circuit: """Apply `visitor` to all instructions in the circuit.""" result = Circuit(self.width) for instr in self.instruction: result.instruction.append(instr.visit(visitor)) return result def map_angle(self, f: Callable[[ParameterizedAngle], ParameterizedAngle]) -> Circuit: """Apply `f` to all angles that occur in the circuit.""" return self.visit(_MapAngleVisitor(f)) def is_parameterized(self) -> bool: """ Return `True` if there is at least one measurement angle that is not just an instance of `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 `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 def subs(self, variable: Parameter, substitute: ExpressionOrFloat) -> Circuit: """Return a copy of the circuit where all occurrences of the given variable in measurement angles are substituted by the given value.""" return self.map_angle(lambda angle: parameter.subs(angle, variable, substitute)) def xreplace(self, assignment: Mapping[Parameter, ExpressionOrFloat]) -> Circuit: """Return a copy of the circuit where all occurrences of the given keys in measurement angles are substituted by the given values in parallel.""" return self.map_angle(lambda angle: parameter.xreplace(angle, assignment)) def transpile_measurements_to_z_axis(self) -> Circuit: """Return an equivalent circuit where all measurements are on Z axis.""" circuit = Circuit(width=self.width) for instr in self.instruction: if instr.kind == InstructionKind.M: match instr.axis: case Axis.X: circuit.h(instr.target) circuit.m(instr.target, Axis.Z) case Axis.Y: circuit.rx(instr.target, ANGLE_PI / 2) circuit.m(instr.target, Axis.Z) case Axis.Z: circuit.add(instr) case _: assert_never(instr.axis) else: circuit.add(instr) return circuit
def _transpile_rzz(instructions: Iterable[Instruction]) -> Iterator[InstructionWithoutRZZ]: for instr in instructions: if instr.kind == InstructionKind.RZZ: yield instruction.CNOT(control=instr.control, target=instr.target) yield instruction.RZ(target=instr.target, angle=instr.angle) yield instruction.CNOT(control=instr.control, target=instr.target) else: yield instr @dataclass(frozen=True) class TranspileSwapsResult: """The result returned by :func:`transpile_swaps`.""" circuit: Circuit """Circuit without SWAP gates.""" qubits: tuple[int | None, ...] """ 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 returned circuit. Measured qubits are mapped to ``None`` in this tuple. """ class _TranspileSwapVisitor(InstructionVisitor): qubits: list[int | None] def __init__(self, width: int) -> None: self.qubits = list(range(width)) @override def visit_qubit(self, qubit: int) -> int: return _check_target(self.qubits, qubit) def transpile_swaps(circuit: Circuit) -> TranspileSwapsResult: """Return a new circuit equivalent to the original one but without SWAP gates. Parameters ---------- circuit : Circuit The original circuit Returns ------- TranspileSwapsResult The field ``circuit`` contains an equivalent circuit without SWAP gates. The field ``qubits`` contains a 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 returned circuit. Measured qubits are mapped to ``None`` in this tuple. """ new_circuit = Circuit(circuit.width) visitor = _TranspileSwapVisitor(circuit.width) for instr in circuit.instruction: if instr.kind == InstructionKind.SWAP: u, v = instr.targets visitor.qubits[u], visitor.qubits[v] = visitor.qubits[v], visitor.qubits[u] else: new_circuit.add(instr.visit(visitor)) if instr.kind == InstructionKind.M: visitor.qubits[instr.target] = None return TranspileSwapsResult(new_circuit, tuple(visitor.qubits))