"""Gate-to-MBQC transpiler
accepts desired gate operations and transpile into MBQC measurement patterns.
"""
from __future__ import annotations
import dataclasses
from copy import deepcopy
from typing import Optional, Sequence
import numpy as np
import graphix.pauli
import graphix.sim.base_backend
from graphix.ops import Ops
from graphix.pattern import Pattern
from graphix.sim.statevec import Statevec
[docs]@dataclasses.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]@dataclasses.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, ...]
[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.
"""
[docs] def __init__(self, width: int):
"""
Parameters
----------
width : int
number of logical qubits for the gate network
"""
self.width = width
self.instruction = []
self.active_qubits = set(range(width))
[docs] def cnot(self, control: int, target: int):
"""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(["CNOT", [control, target]])
def swap(self, qubit1: int, qubit2: int):
"""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(["SWAP", [qubit1, qubit2]])
[docs] def h(self, qubit: int):
"""Hadamard gate
Parameters
---------
qubit : int
target qubit
"""
assert qubit in self.active_qubits
self.instruction.append(["H", qubit])
[docs] def s(self, qubit: int):
"""S gate
Parameters
---------
qubit : int
target qubit
"""
assert qubit in self.active_qubits
self.instruction.append(["S", qubit])
[docs] def x(self, qubit):
"""Pauli X gate
Parameters
---------
qubit : int
target qubit
"""
assert qubit in self.active_qubits
self.instruction.append(["X", qubit])
[docs] def y(self, qubit: int):
"""Pauli Y gate
Parameters
---------
qubit : int
target qubit
"""
assert qubit in self.active_qubits
self.instruction.append(["Y", qubit])
[docs] def z(self, qubit: int):
"""Pauli Z gate
Parameters
---------
qubit : int
target qubit
"""
assert qubit in self.active_qubits
self.instruction.append(["Z", qubit])
[docs] def rx(self, qubit: int, angle: float):
"""X rotation gate
Parameters
---------
qubit : int
target qubit
angle : float
rotation angle in radian
"""
assert qubit in self.active_qubits
self.instruction.append(["Rx", qubit, angle])
[docs] def ry(self, qubit: int, angle: float):
"""Y rotation gate
Parameters
---------
qubit : int
target qubit
angle : float
angle in radian
"""
assert qubit in self.active_qubits
self.instruction.append(["Ry", qubit, angle])
[docs] def rz(self, qubit: int, angle: float):
"""Z rotation gate
Parameters
---------
qubit : int
target qubit
angle : float
rotation angle in radian
"""
assert qubit in self.active_qubits
self.instruction.append(["Rz", qubit, angle])
def rzz(self, control: int, target: int, angle: float):
r"""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 : float
rotation angle in radian
"""
assert control in self.active_qubits
assert target in self.active_qubits
self.instruction.append(["Rzz", [control, target], angle])
[docs] def ccx(self, control1: int, control2: int, target: int):
r"""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
self.instruction.append(["CCX", [control1, control2, target]])
def i(self, qubit: int):
"""identity (teleportation) gate
Parameters
---------
qubit : int
target qubit
"""
assert qubit in self.active_qubits
self.instruction.append(["I", qubit])
[docs] def m(self, qubit: int, plane: graphix.pauli.Plane, angle: float):
"""measure a quantum qubit
The measured qubit cannot be used afterwards.
Parameters
---------
qubit : int
target qubit
plane : graphix.pauli.Plane
angle : float
"""
assert qubit in self.active_qubits
self.instruction.append(["M", qubit, plane, angle])
self.active_qubits.remove(qubit)
[docs] def transpile(self, opt: bool = False) -> TranspileResult:
"""gate-to-MBQC transpile function.
Parameters
----------
opt : bool
Whether or not to use pre-optimized gateset with local-Clifford decoration.
Returns
--------
result : :class:`TranspileResult` object
"""
Nnode = self.width
input = [j for j in range(self.width)]
out = [j for j in range(self.width)]
pattern = Pattern(input_nodes=input)
classical_outputs = []
for instr in self.instruction:
if instr[0] == "CNOT":
ancilla = [Nnode, Nnode + 1]
assert out[instr[1][0]] is not None
assert out[instr[1][1]] is not None
out[instr[1][0]], out[instr[1][1]], seq = self._cnot_command(
out[instr[1][0]], out[instr[1][1]], ancilla
)
pattern.extend(seq)
Nnode += 2
elif instr[0] == "SWAP":
assert out[instr[1][0]] is not None
assert out[instr[1][1]] is not None
out[instr[1][0]], out[instr[1][1]] = out[instr[1][1]], out[instr[1][0]]
elif instr[0] == "I":
pass
elif instr[0] == "H":
ancilla = Nnode
assert out[instr[1]] is not None
out[instr[1]], seq = self._h_command(out[instr[1]], ancilla)
pattern.extend(seq)
Nnode += 1
elif instr[0] == "S":
ancilla = [Nnode, Nnode + 1]
assert out[instr[1]] is not None
out[instr[1]], seq = self._s_command(out[instr[1]], ancilla)
pattern.extend(seq)
Nnode += 2
elif instr[0] == "X":
ancilla = [Nnode, Nnode + 1]
assert out[instr[1]] is not None
out[instr[1]], seq = self._x_command(out[instr[1]], ancilla)
pattern.extend(seq)
Nnode += 2
elif instr[0] == "Y":
ancilla = [Nnode, Nnode + 1, Nnode + 2, Nnode + 3]
assert out[instr[1]] is not None
out[instr[1]], seq = self._y_command(out[instr[1]], ancilla)
pattern.extend(seq)
Nnode += 4
elif instr[0] == "Z":
ancilla = [Nnode, Nnode + 1]
assert out[instr[1]] is not None
out[instr[1]], seq = self._z_command(out[instr[1]], ancilla)
pattern.extend(seq)
Nnode += 2
elif instr[0] == "Rx":
ancilla = [Nnode, Nnode + 1]
assert out[instr[1]] is not None
out[instr[1]], seq = self._rx_command(out[instr[1]], ancilla, instr[2])
pattern.extend(seq)
Nnode += 2
elif instr[0] == "Ry":
ancilla = [Nnode, Nnode + 1, Nnode + 2, Nnode + 3]
assert out[instr[1]] is not None
out[instr[1]], seq = self._ry_command(out[instr[1]], ancilla, instr[2])
pattern.extend(seq)
Nnode += 4
elif instr[0] == "Rz":
assert out[instr[1]] is not None
if opt:
ancilla = Nnode
out[instr[1]], seq = self._rz_command_opt(out[instr[1]], ancilla, instr[2])
pattern.extend(seq)
Nnode += 1
else:
ancilla = [Nnode, Nnode + 1]
out[instr[1]], seq = self._rz_command(out[instr[1]], ancilla, instr[2])
pattern.extend(seq)
Nnode += 2
elif instr[0] == "Rzz":
assert out[instr[1][0]] is not None
assert out[instr[1][1]] is not None
if opt:
ancilla = Nnode
out[instr[1][0]], out[instr[1][1]], seq = self._rzz_command_opt(
out[instr[1][0]], out[instr[1][1]], ancilla, instr[2]
)
pattern.extend(seq)
Nnode += 1
else:
raise NotImplementedError(
"YZ-plane measurements not accepted and Rzz gate\
cannot be directly transpiled"
)
elif instr[0] == "CCX":
if opt:
ancilla = [Nnode + i for i in range(11)]
(
out[instr[1][0]],
out[instr[1][1]],
out[instr[1][2]],
seq,
) = self._ccx_command_opt(out[instr[1][0]], out[instr[1][1]], out[instr[1][2]], ancilla)
pattern.extend(seq)
Nnode += 11
else:
ancilla = [Nnode + i for i in range(18)]
(
out[instr[1][0]],
out[instr[1][1]],
out[instr[1][2]],
seq,
) = self._ccx_command(out[instr[1][0]], out[instr[1][1]], out[instr[1][2]], ancilla)
pattern.extend(seq)
Nnode += 18
elif instr[0] == "M":
_, circuit_index, plane, angle = instr
node_index = out[circuit_index]
assert node_index is not None
seq = self._m_command(node_index, plane, angle)
pattern.extend(seq)
classical_outputs.append(node_index)
out[instr[1]] = None
else:
raise ValueError("Unknown instruction, commands not added")
out = filter(lambda node: node is not None, out)
pattern.reorder_output_nodes(out)
return TranspileResult(pattern, tuple(classical_outputs))
[docs] def standardize_and_transpile(self, opt: bool = True) -> TranspileResult:
"""gate-to-MBQC transpile function.
Commutes all byproduct through gates, instead of through measurement
commands, to generate standardized measurement pattern.
Parameters
----------
opt : bool
Whether or not to use pre-optimized gateset with local-Clifford decoration.
Returns
--------
pattern : :class:`graphix.pattern.Pattern` object
"""
self._N = []
# for i in range(self.width):
# self._N.append(["N", i])
self._M = []
self._E = []
self._instr = []
Nnode = self.width
inputs = [j for j in range(self.width)]
out = [j for j in range(self.width)]
classical_outputs = []
for instr in self.instruction:
if instr[0] == "CNOT":
ancilla = [Nnode, Nnode + 1]
assert out[instr[1][0]] is not None
assert out[instr[1][1]] is not None
out[instr[1][0]], out[instr[1][1]], seq = self._cnot_command(
out[instr[1][0]], out[instr[1][1]], ancilla
)
self._N.extend(seq[0:2])
self._E.extend(seq[2:5])
self._M.extend(seq[5:7])
Nnode += 2
self._instr.append(instr)
self._instr.append(["XC", instr[1][1], seq[7][2]])
self._instr.append(["ZC", instr[1][1], seq[8][2]])
self._instr.append(["ZC", instr[1][0], seq[9][2]])
elif instr[0] == "SWAP":
assert out[instr[1][0]] is not None
assert out[instr[1][1]] is not None
out[instr[1][0]], out[instr[1][1]] = out[instr[1][1]], out[instr[1][0]]
self._instr.append(instr)
elif instr[0] == "I":
pass
elif instr[0] == "H":
ancilla = Nnode
assert out[instr[1]] is not None
out[instr[1]], seq = self._h_command(out[instr[1]], ancilla)
self._N.append(seq[0])
self._E.append(seq[1])
self._M.append(seq[2])
self._instr.append(instr)
self._instr.append(["XC", instr[1], seq[3][2]])
Nnode += 1
elif instr[0] == "S":
ancilla = [Nnode, Nnode + 1]
assert out[instr[1]] is not None
out[instr[1]], seq = self._s_command(out[instr[1]], ancilla)
self._N.extend(seq[0:2])
self._E.extend(seq[2:4])
self._M.extend(seq[4:6])
self._instr.append(instr)
self._instr.append(["XC", instr[1], seq[6][2]])
self._instr.append(["ZC", instr[1], seq[7][2]])
Nnode += 2
elif instr[0] == "X":
ancilla = [Nnode, Nnode + 1]
assert out[instr[1]] is not None
out[instr[1]], seq = self._x_command(out[instr[1]], ancilla)
self._N.extend(seq[0:2])
self._E.extend(seq[2:4])
self._M.extend(seq[4:6])
self._instr.append(instr)
self._instr.append(["XC", instr[1], seq[6][2]])
self._instr.append(["ZC", instr[1], seq[7][2]])
Nnode += 2
elif instr[0] == "Y":
ancilla = [Nnode, Nnode + 1, Nnode + 2, Nnode + 3]
assert out[instr[1]] is not None
out[instr[1]], seq = self._y_command(out[instr[1]], ancilla)
self._N.extend(seq[0:4])
self._E.extend(seq[4:8])
self._M.extend(seq[8:12])
self._instr.append(instr)
self._instr.append(["XC", instr[1], seq[12][2]])
self._instr.append(["ZC", instr[1], seq[13][2]])
Nnode += 4
elif instr[0] == "Z":
ancilla = [Nnode, Nnode + 1]
assert out[instr[1]] is not None
out[instr[1]], seq = self._z_command(out[instr[1]], ancilla)
self._N.extend(seq[0:2])
self._E.extend(seq[2:4])
self._M.extend(seq[4:6])
self._instr.append(instr)
self._instr.append(["XC", instr[1], seq[6][2]])
self._instr.append(["ZC", instr[1], seq[7][2]])
Nnode += 2
elif instr[0] == "Rx":
ancilla = [Nnode, Nnode + 1]
assert out[instr[1]] is not None
out[instr[1]], seq = self._rx_command(out[instr[1]], ancilla, instr[2])
self._N.extend(seq[0:2])
self._E.extend(seq[2:4])
self._M.extend(seq[4:6])
instr_ = deepcopy(instr)
instr_.append(len(self._M) - 1) # index of arb angle measurement command
self._instr.append(instr_)
self._instr.append(["XC", instr[1], seq[6][2]])
self._instr.append(["ZC", instr[1], seq[7][2]])
Nnode += 2
elif instr[0] == "Ry":
ancilla = [Nnode, Nnode + 1, Nnode + 2, Nnode + 3]
assert out[instr[1]] is not None
out[instr[1]], seq = self._ry_command(out[instr[1]], ancilla, instr[2])
self._N.extend(seq[0:4])
self._E.extend(seq[4:8])
self._M.extend(seq[8:12])
instr_ = deepcopy(instr)
instr_.append(len(self._M) - 3) # index of arb angle measurement command
self._instr.append(instr_)
self._instr.append(["XC", instr[1], seq[12][2]])
self._instr.append(["ZC", instr[1], seq[13][2]])
Nnode += 4
elif instr[0] == "Rz":
assert out[instr[1]] is not None
if opt:
ancilla = Nnode
out[instr[1]], seq = self._rz_command_opt(out[instr[1]], ancilla, instr[2])
self._N.append(seq[0])
self._E.append(seq[1])
self._M.append(seq[2])
instr_ = deepcopy(instr)
instr_.append(len(self._M) - 1) # index of arb angle measurement command
self._instr.append(instr_)
self._instr.append(["ZC", instr[1], seq[3][2]])
Nnode += 1
else:
ancilla = [Nnode, Nnode + 1]
out[instr[1]], seq = self._rz_command(out[instr[1]], ancilla, instr[2])
self._N.extend(seq[0:2])
self._E.extend(seq[2:4])
self._M.extend(seq[4:6])
instr_ = deepcopy(instr)
instr_.append(len(self._M) - 2) # index of arb angle measurement command
self._instr.append(instr_)
self._instr.append(["XC", instr[1], seq[6][2]])
self._instr.append(["ZC", instr[1], seq[7][2]])
Nnode += 2
elif instr[0] == "Rzz":
assert out[instr[1][0]] is not None
assert out[instr[1][1]] is not None
ancilla = Nnode
out[instr[1][0]], out[instr[1][1]], seq = self._rzz_command_opt(
out[instr[1][0]], out[instr[1][1]], ancilla, instr[2]
)
self._N.append(seq[0])
self._E.extend(seq[1:3])
self._M.append(seq[3])
Nnode += 1
instr_ = deepcopy(instr)
instr_.append(len(self._M) - 1) # index of arb angle measurement command
self._instr.append(instr_)
self._instr.append(["ZC", instr[1][1], seq[4][2]])
self._instr.append(["ZC", instr[1][0], seq[5][2]])
elif instr[0] == "M":
node_index = out[instr[1]]
assert node_index is not None
seq = self._m_command(node_index)
classical_outputs.append(node_index)
out[instr[1]] = None
else:
raise ValueError("Unknown instruction, commands not added")
# move xc, zc to the end of the self._instr, so they will be applied last
self._move_byproduct_to_right()
# create command sequence
command_seq = []
for cmd in self._N:
command_seq.append(cmd)
for cmd in reversed(self._E):
command_seq.append(cmd)
for cmd in self._M:
command_seq.append(cmd)
bpx_added = dict()
bpz_added = dict()
# byproduct command buffer
z_cmds = []
x_cmds = []
for i in range(len(self._instr)):
instr = self._instr[i]
if instr[0] == "XC":
if instr[1] in bpx_added.keys():
x_cmds[bpx_added[instr[1]]][2].extend(instr[2])
else:
bpx_added[instr[1]] = len(x_cmds)
x_cmds.append(["X", out[instr[1]], deepcopy(instr[2])])
elif instr[0] == "ZC":
if instr[1] in bpz_added.keys():
z_cmds[bpz_added[instr[1]]][2].extend(instr[2])
else:
bpz_added[instr[1]] = len(z_cmds)
z_cmds.append(["Z", out[instr[1]], deepcopy(instr[2])])
# append z commands first (X and Z commute up to global phase)
for cmd in z_cmds:
command_seq.append(cmd)
for cmd in x_cmds:
command_seq.append(cmd)
pattern = Pattern(input_nodes=inputs)
pattern.extend(command_seq)
out = filter(lambda node: node is not None, out)
pattern.reorder_output_nodes(out)
return TranspileResult(pattern, classical_outputs)
def _commute_with_swap(self, target: int):
assert self._instr[target][0] in ["XC", "ZC"]
assert self._instr[target + 1][0] == "SWAP"
if self._instr[target][1] == self._instr[target + 1][1][0]:
self._instr[target][1] = self._instr[target + 1][1][1]
self._commute_with_following(target)
elif self._instr[target][1] == self._instr[target + 1][1][1]:
self._instr[target][1] = self._instr[target + 1][1][0]
self._commute_with_following(target)
else:
self._commute_with_following(target)
return target
def _commute_with_cnot(self, target: int):
assert self._instr[target][0] in ["XC", "ZC"]
assert self._instr[target + 1][0] == "CNOT"
if self._instr[target][0] == "XC" and self._instr[target][1] == self._instr[target + 1][1][0]: # control
new_cmd = ["XC", self._instr[target + 1][1][1], self._instr[target][2]]
self._commute_with_following(target)
self._instr.insert(target + 1, new_cmd)
return target + 1
elif self._instr[target][0] == "ZC" and self._instr[target][1] == self._instr[target + 1][1][1]: # target
new_cmd = ["ZC", self._instr[target + 1][1][0], self._instr[target][2]]
self._commute_with_following(target)
self._instr.insert(target + 1, new_cmd)
return target + 1
else:
self._commute_with_following(target)
return target
def _commute_with_H(self, target: int):
assert self._instr[target][0] in ["XC", "ZC"]
assert self._instr[target + 1][0] == "H"
if self._instr[target][1] == self._instr[target + 1][1]:
if self._instr[target][0] == "XC":
self._instr[target][0] = "ZC" # byproduct changes to Z
self._commute_with_following(target)
else:
self._instr[target][0] = "XC" # byproduct changes to X
self._commute_with_following(target)
else:
self._commute_with_following(target)
def _commute_with_S(self, target: int):
assert self._instr[target][0] in ["XC", "ZC"]
assert self._instr[target + 1][0] == "S"
if self._instr[target][1] == self._instr[target + 1][1]:
if self._instr[target][0] == "XC":
self._commute_with_following(target)
# changes to Y = XZ
self._instr.insert(target + 1, ["ZC", self._instr[target + 1][1], self._instr[target + 1][2]])
return target + 1
self._commute_with_following(target)
return target
def _commute_with_Rx(self, target: int):
assert self._instr[target][0] in ["XC", "ZC"]
assert self._instr[target + 1][0] == "Rx"
if self._instr[target][1] == self._instr[target + 1][1]:
if self._instr[target][0] == "ZC":
# add to the s-domain
self._M[self._instr[target + 1][3]][4].extend(self._instr[target][2])
self._commute_with_following(target)
else:
self._commute_with_following(target)
else:
self._commute_with_following(target)
def _commute_with_Ry(self, target: int):
assert self._instr[target][0] in ["XC", "ZC"]
assert self._instr[target + 1][0] == "Ry"
if self._instr[target][1] == self._instr[target + 1][1]:
# add to the s-domain
self._M[self._instr[target + 1][3]][4].extend(self._instr[target][2])
self._commute_with_following(target)
else:
self._commute_with_following(target)
def _commute_with_Rz(self, target: int):
assert self._instr[target][0] in ["XC", "ZC"]
assert self._instr[target + 1][0] == "Rz"
if self._instr[target][1] == self._instr[target + 1][1]:
if self._instr[target][0] == "XC":
# add to the s-domain
self._M[self._instr[target + 1][3]][4].extend(self._instr[target][2])
self._commute_with_following(target)
else:
self._commute_with_following(target)
else:
self._commute_with_following(target)
def _commute_with_Rzz(self, target: int):
assert self._instr[target][0] in ["XC", "ZC"]
assert self._instr[target + 1][0] == "Rzz"
if self._instr[target][0] == "XC":
cond = self._instr[target][1] == self._instr[target + 1][1][0]
cond2 = self._instr[target][1] == self._instr[target + 1][1][1]
if cond or cond2:
# add to the s-domain
self._M[self._instr[target + 1][3]][4].extend(self._instr[target][2])
self._commute_with_following(target)
def _commute_with_following(self, target: int):
"""Internal method to perform the commutation of
two consecutive commands that commutes.
commutes the target command with the following command.
Parameters
----------
target : int
target command index
"""
A = self._instr[target + 1]
self._instr.pop(target + 1)
self._instr.insert(target, A)
def _find_byproduct_to_move(self, rev: bool = False, skipnum: int = 0):
"""Internal method for reordering commands
Parameters
----------
rev : bool
search from the end (true) or start (false) of seq
skipnum : int
skip the detected command by specified times
"""
if not rev: # search from the start
target = 0
step = 1
else: # search from the back
target = len(self._instr) - 1
step = -1
ite = 0
num_ops = 0
while ite < len(self._instr):
if self._instr[target][0] in ["ZC", "XC"]:
num_ops += 1
if num_ops == skipnum + 1:
return target
ite += 1
target += step
target = "end"
return target
def _move_byproduct_to_right(self):
"""Internal method to move the byproduct 'gate' to the end of sequence, using the commutation relations"""
moved = 0 # number of moved op
target = self._find_byproduct_to_move(rev=True, skipnum=moved)
while target != "end":
if (target == len(self._instr) - 1) or (self._instr[target + 1][0] in ["XC", "ZC"]):
moved += 1
target = self._find_byproduct_to_move(rev=True, skipnum=moved)
continue
if self._instr[target + 1][0] == "CNOT":
target = self._commute_with_cnot(target)
elif self._instr[target + 1][0] == "SWAP":
target = self._commute_with_swap(target)
elif self._instr[target + 1][0] == "H":
self._commute_with_H(target)
elif self._instr[target + 1][0] == "S":
target = self._commute_with_S(target)
elif self._instr[target + 1][0] == "Rx":
self._commute_with_Rx(target)
elif self._instr[target + 1][0] == "Ry":
self._commute_with_Ry(target)
elif self._instr[target + 1][0] == "Rz":
self._commute_with_Rz(target)
elif self._instr[target + 1][0] == "Rzz":
self._commute_with_Rzz(target)
else:
# Pauli gates commute up to global phase.
self._commute_with_following(target)
target += 1
@classmethod
def _cnot_command(self, control_node: int, target_node: int, ancilla: Sequence[int]):
"""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 = [["N", ancilla[0]], ["N", ancilla[1]]]
seq.append(["E", (target_node, ancilla[0])])
seq.append(["E", (control_node, ancilla[0])])
seq.append(["E", (ancilla[0], ancilla[1])])
seq.append(["M", target_node, "XY", 0, [], []])
seq.append(["M", ancilla[0], "XY", 0, [], []])
seq.append(["X", ancilla[1], [ancilla[0]]])
seq.append(["Z", ancilla[1], [target_node]])
seq.append(["Z", control_node, [target_node]])
return control_node, ancilla[1], seq
@classmethod
def _m_command(self, input_node: int, plane: graphix.pauli.Plane, angle: float):
"""MBQC commands for measuring qubit
Parameters
---------
input_node : int
target node on graph
plane : graphix.pauli.Plane
plane of the measure
angle : float
angle of the measure (unit: pi radian)
Returns
---------
commands : list
list of MBQC commands
"""
seq = [
["M", input_node, plane.name, angle, [], []],
]
return seq
@classmethod
def _h_command(self, input_node: int, ancilla: int):
"""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 = [["N", ancilla]]
seq.append(["E", (input_node, ancilla)])
seq.append(["M", input_node, "XY", 0, [], []])
seq.append(["X", ancilla, [input_node]])
return ancilla, seq
@classmethod
def _s_command(self, input_node: int, ancilla: Sequence[int]):
"""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 = [["N", ancilla[0]], ["N", ancilla[1]]]
seq.append(["E", (input_node, ancilla[0])])
seq.append(["E", (ancilla[0], ancilla[1])])
seq.append(["M", input_node, "XY", -0.5, [], []])
seq.append(["M", ancilla[0], "XY", 0, [], []])
seq.append(["X", ancilla[1], [ancilla[0]]])
seq.append(["Z", ancilla[1], [input_node]])
return ancilla[1], seq
@classmethod
def _x_command(self, input_node: int, ancilla: Sequence[int]):
"""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 = [["N", ancilla[0]], ["N", ancilla[1]]]
seq.append(["E", (input_node, ancilla[0])])
seq.append(["E", (ancilla[0], ancilla[1])])
seq.append(["M", input_node, "XY", 0, [], []])
seq.append(["M", ancilla[0], "XY", -1, [], []])
seq.append(["X", ancilla[1], [ancilla[0]]])
seq.append(["Z", ancilla[1], [input_node]])
return ancilla[1], seq
@classmethod
def _y_command(self, input_node: int, ancilla: Sequence[int]):
"""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 = [["N", ancilla[0]], ["N", ancilla[1]]] # assign new qubit labels
seq.extend([["N", ancilla[2]], ["N", ancilla[3]]])
seq.append(["E", (input_node, ancilla[0])])
seq.append(["E", (ancilla[0], ancilla[1])])
seq.append(["E", (ancilla[1], ancilla[2])])
seq.append(["E", (ancilla[2], ancilla[3])])
seq.append(["M", input_node, "XY", 0.5, [], []])
seq.append(["M", ancilla[0], "XY", 1.0, [input_node], []])
seq.append(["M", ancilla[1], "XY", -0.5, [input_node], []])
seq.append(["M", ancilla[2], "XY", 0, [], []])
seq.append(["X", ancilla[3], [ancilla[0], ancilla[2]]])
seq.append(["Z", ancilla[3], [ancilla[0], ancilla[1]]])
return ancilla[3], seq
@classmethod
def _z_command(self, input_node: int, ancilla: Sequence[int]):
"""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 = [["N", ancilla[0]], ["N", ancilla[1]]] # assign new qubit labels
seq.append(["E", (input_node, ancilla[0])])
seq.append(["E", (ancilla[0], ancilla[1])])
seq.append(["M", input_node, "XY", -1, [], []])
seq.append(["M", ancilla[0], "XY", 0, [], []])
seq.append(["X", ancilla[1], [ancilla[0]]])
seq.append(["Z", ancilla[1], [input_node]])
return ancilla[1], seq
@classmethod
def _rx_command(self, input_node: int, ancilla: Sequence[int], angle: float):
"""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 : float
measurement angle in radian
Returns
---------
out_node : int
control node on graph after the gate
commands : list
list of MBQC commands
"""
assert len(ancilla) == 2
seq = [["N", ancilla[0]], ["N", ancilla[1]]] # assign new qubit labels
seq.append(["E", (input_node, ancilla[0])])
seq.append(["E", (ancilla[0], ancilla[1])])
seq.append(["M", input_node, "XY", 0, [], []])
seq.append(["M", ancilla[0], "XY", -1 * angle / np.pi, [input_node], []])
seq.append(["X", ancilla[1], [ancilla[0]]])
seq.append(["Z", ancilla[1], [input_node]])
return ancilla[1], seq
@classmethod
def _ry_command(self, input_node: int, ancilla: Sequence[int], angle: float):
"""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 : float
rotation angle in radian
Returns
---------
out_node : int
control node on graph after the gate
commands : list
list of MBQC commands
"""
assert len(ancilla) == 4
seq = [["N", ancilla[0]], ["N", ancilla[1]]] # assign new qubit labels
seq.extend([["N", ancilla[2]], ["N", ancilla[3]]])
seq.append(["E", (input_node, ancilla[0])])
seq.append(["E", (ancilla[0], ancilla[1])])
seq.append(["E", (ancilla[1], ancilla[2])])
seq.append(["E", (ancilla[2], ancilla[3])])
seq.append(["M", input_node, "XY", 0.5, [], []])
seq.append(["M", ancilla[0], "XY", -1 * angle / np.pi, [input_node], []])
seq.append(["M", ancilla[1], "XY", -0.5, [input_node], []])
seq.append(["M", ancilla[2], "XY", 0, [], []])
seq.append(["X", ancilla[3], [ancilla[0], ancilla[2]]])
seq.append(["Z", ancilla[3], [ancilla[0], ancilla[1]]])
return ancilla[3], seq
@classmethod
def _rz_command(self, input_node: int, ancilla: Sequence[int], angle: float):
"""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 : float
measurement angle in radian
Returns
---------
out_node : int
node on graph after the gate
commands : list
list of MBQC commands
"""
assert len(ancilla) == 2
seq = [["N", ancilla[0]], ["N", ancilla[1]]] # assign new qubit labels
seq.append(["E", (input_node, ancilla[0])])
seq.append(["E", (ancilla[0], ancilla[1])])
seq.append(["M", input_node, "XY", -1 * angle / np.pi, [], []])
seq.append(["M", ancilla[0], "XY", 0, [], []])
seq.append(["X", ancilla[1], [ancilla[0]]])
seq.append(["Z", ancilla[1], [input_node]])
return ancilla[1], seq
@classmethod
def _rz_command_opt(self, input_node: int, ancilla: int, angle: float):
"""optimized MBQC commands for Z rotation gate
Parameters
---------
input_node : int
input node index
ancilla : int
ancilla node index to be added to graph
angle : float
measurement angle in radian
Returns
---------
out_node : int
control node on graph after the gate
commands : list
list of MBQC commands
"""
seq = [["N", ancilla]] # assign new qubit label
seq.append(["E", (input_node, ancilla)])
seq.append(["M", ancilla, "XY", -angle / np.pi, [], [], 6])
seq.append(["Z", input_node, [ancilla]])
return input_node, seq
@classmethod
def _rzz_command_opt(self, control_node: int, target_node: int, ancilla: int, angle: float):
"""Optimized MBQC commands for ZZ-rotation gate
Parameters
---------
input_node : int
input node index
ancilla : int
ancilla node index
angle : float
measurement angle in radian
Returns
---------
out_node_control : int
control node on graph after the gate
out_node_target : int
target node on graph after the gate
commands : list
list of MBQC commands
"""
seq = [["N", ancilla]] # assign new qubit labels
seq.append(["E", (control_node, ancilla)])
seq.append(["E", (target_node, ancilla)])
seq.append(["M", ancilla, "XY", -angle / np.pi, [], [], 6])
seq.append(["Z", control_node, [ancilla]])
seq.append(["Z", target_node, [ancilla]])
return control_node, target_node, seq
@classmethod
def _ccx_command(self, control_node1: int, control_node2: int, target_node: int, ancilla: Sequence[int]):
"""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 = [["N", ancilla[i]] for i in range(18)] # assign new qubit labels
seq.append(["E", (target_node, ancilla[0])])
seq.append(["E", (ancilla[0], ancilla[1])])
seq.append(["E", (ancilla[1], ancilla[2])])
seq.append(["E", (ancilla[1], control_node2)])
seq.append(["E", (control_node1, ancilla[14])])
seq.append(["E", (ancilla[2], ancilla[3])])
seq.append(["E", (ancilla[14], ancilla[4])])
seq.append(["E", (ancilla[3], ancilla[5])])
seq.append(["E", (ancilla[3], ancilla[4])])
seq.append(["E", (ancilla[5], ancilla[6])])
seq.append(["E", (control_node2, ancilla[6])])
seq.append(["E", (control_node2, ancilla[9])])
seq.append(["E", (ancilla[6], ancilla[7])])
seq.append(["E", (ancilla[9], ancilla[4])])
seq.append(["E", (ancilla[9], ancilla[10])])
seq.append(["E", (ancilla[7], ancilla[8])])
seq.append(["E", (ancilla[10], ancilla[11])])
seq.append(["E", (ancilla[4], ancilla[8])])
seq.append(["E", (ancilla[4], ancilla[11])])
seq.append(["E", (ancilla[4], ancilla[16])])
seq.append(["E", (ancilla[8], ancilla[12])])
seq.append(["E", (ancilla[11], ancilla[15])])
seq.append(["E", (ancilla[12], ancilla[13])])
seq.append(["E", (ancilla[16], ancilla[17])])
seq.append(["M", target_node, "XY", -0.0, [], []])
seq.append(["M", ancilla[0], "XY", -0.0, [target_node], []])
seq.append(["M", ancilla[1], "XY", -0.0, [ancilla[0]], []])
seq.append(["M", control_node1, "XY", -0.0, [], []])
seq.append(["M", ancilla[2], "XY", -1.75, [ancilla[1], target_node], []])
seq.append(["M", ancilla[14], "XY", -0.0, [control_node1], []])
seq.append(["M", ancilla[3], "XY", -0.0, [ancilla[2], ancilla[0]], []])
seq.append(["M", ancilla[5], "XY", -0.25, [ancilla[3], ancilla[1], ancilla[14], target_node], []])
seq.append(["M", control_node2, "XY", -0.25, [], []])
seq.append(["M", ancilla[6], "XY", -0.0, [ancilla[5], ancilla[2], ancilla[0]], []])
seq.append(["M", ancilla[9], "XY", -0.0, [control_node2, ancilla[0], ancilla[5], ancilla[2], ancilla[0]], []])
seq.append(["M", ancilla[7], "XY", -1.75, [ancilla[6], ancilla[3], ancilla[1], ancilla[14], target_node], []])
seq.append(["M", ancilla[10], "XY", -1.75, [ancilla[9], ancilla[14]], []])
seq.append(["M", ancilla[4], "XY", -0.25, [ancilla[14]], []])
seq.append(["M", ancilla[8], "XY", -0.0, [ancilla[7], ancilla[5], ancilla[2], ancilla[0]], []])
seq.append(
[
"M",
ancilla[11],
"XY",
-0.0,
[ancilla[10], control_node2, ancilla[0], ancilla[5], ancilla[2], ancilla[0]],
[],
]
)
seq.append(
[
"M",
ancilla[12],
"XY",
-0.25,
[ancilla[8], ancilla[14], ancilla[6], ancilla[3], ancilla[1], ancilla[14], target_node],
[],
]
)
seq.append(
[
"M",
ancilla[16],
"XY",
-0.0,
[
ancilla[4],
control_node1,
ancilla[2],
control_node2,
ancilla[7],
ancilla[10],
ancilla[0],
ancilla[0],
ancilla[5],
ancilla[2],
ancilla[0],
ancilla[5],
ancilla[2],
ancilla[0],
control_node2,
ancilla[0],
ancilla[5],
ancilla[2],
ancilla[0],
],
[],
]
)
seq.append(["X", ancilla[17], [ancilla[14], ancilla[16]]])
seq.append(["X", ancilla[15], [ancilla[9], ancilla[11]]])
seq.append(["X", ancilla[13], [ancilla[0], ancilla[2], ancilla[5], ancilla[7], ancilla[12]]])
seq.append(["Z", ancilla[17], [ancilla[4], ancilla[5], ancilla[7], ancilla[10], control_node1]])
seq.append(["Z", ancilla[15], [control_node2, ancilla[2], ancilla[5], ancilla[10]]])
seq.append(["Z", ancilla[13], [ancilla[1], ancilla[3], ancilla[6], ancilla[8], target_node]])
return ancilla[17], ancilla[15], ancilla[13], seq
@classmethod
def _ccx_command_opt(self, control_node1: int, control_node2: int, target_node: int, ancilla: Sequence[int]):
"""Optimized 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) == 11
seq = [["N", ancilla[i]] for i in range(11)] # assign new qubit labels
seq.append(["E", (control_node1, ancilla[8])])
seq.append(["E", (control_node2, ancilla[4])])
seq.append(["E", (control_node2, ancilla[5])])
seq.append(["E", (control_node2, ancilla[2])])
seq.append(["E", (control_node2, ancilla[0])])
seq.append(["E", (target_node, ancilla[6])])
seq.append(["E", (ancilla[0], ancilla[6])])
seq.append(["E", (ancilla[1], ancilla[10])])
seq.append(["E", (ancilla[2], ancilla[10])])
seq.append(["E", (ancilla[2], ancilla[6])])
seq.append(["E", (ancilla[3], ancilla[6])])
seq.append(["E", (ancilla[3], ancilla[10])])
seq.append(["E", (ancilla[4], ancilla[10])])
seq.append(["E", (ancilla[5], ancilla[9])])
seq.append(["E", (ancilla[6], ancilla[7])])
seq.append(["E", (ancilla[8], ancilla[10])])
seq.append(["M", target_node, "XY", -0.0, [], []])
seq.append(["M", control_node1, "XY", -0.0, [], []])
seq.append(["M", ancilla[0], "XY", -1.75, [target_node], [], 6])
seq.append(["M", ancilla[8], "XY", -0.0, [control_node1], []])
seq.append(["M", ancilla[2], "XY", -0.25, [target_node, ancilla[8]], [], 6])
seq.append(["M", control_node2, "XY", -0.25, [], []])
seq.append(["M", ancilla[3], "XY", -1.75, [ancilla[8], target_node], [], 6])
seq.append(["M", ancilla[4], "XY", -1.75, [ancilla[8]], [], 6])
seq.append(["M", ancilla[1], "XY", -0.25, [ancilla[8]], [], 6])
seq.append(["M", ancilla[5], "XY", -0.0, [control_node2, ancilla[0], ancilla[2], ancilla[4]], []])
seq.append(["M", ancilla[6], "XY", -0.25, [target_node], []])
seq.append(["X", ancilla[10], [ancilla[8]]])
seq.append(["X", ancilla[9], [ancilla[5]]])
seq.append(["X", ancilla[7], [ancilla[0], ancilla[2], ancilla[3], ancilla[6]]])
seq.append(["Z", ancilla[10], [control_node1, ancilla[1], ancilla[2], ancilla[3], ancilla[4]]])
seq.append(["Z", ancilla[9], [control_node2, ancilla[0], ancilla[2], ancilla[4]]])
seq.append(["Z", ancilla[7], [target_node]])
return ancilla[10], ancilla[9], ancilla[7], seq
@classmethod
def _sort_outputs(self, pattern: Pattern, output_nodes: Sequence[int]):
"""Sort the node indices of ouput qubits.
Parameters
---------
pattern : :meth:`~graphix.pattern.Pattern`
pattern object
output_nodes : list of int
output node indices
Returns
---------
out_node : int
control node on graph after the gate
commands : list
list of MBQC commands
"""
old_out = deepcopy(output_nodes)
output_nodes.sort()
# check all commands and swap node indices
for cmd in pattern:
if cmd[0] == "E":
j, k = cmd[1]
if j in old_out:
j = output_nodes[old_out.index(j)]
if k in old_out:
k = output_nodes[old_out.index(k)]
cmd[1] = (j, k)
elif cmd[1] in old_out:
cmd[1] = output_nodes[old_out.index(cmd[1])]
[docs] def simulate_statevector(self, input_state: Optional[Statevec] = None) -> SimulateResult:
"""Run statevector simultion of the gate sequence, using graphix.Statevec
Parameters
----------
input_state : :class:`graphix.Statevec`
Returns
-------
result : :class:`SimulateResult`
output state of the statevector simulation and results of classical measures.
"""
if input_state is None:
state = Statevec(nqubit=self.width)
else:
state = input_state
classical_measures = []
for i in range(len(self.instruction)):
if self.instruction[i][0] == "CNOT":
state.CNOT((self.instruction[i][1][0], self.instruction[i][1][1]))
elif self.instruction[i][0] == "SWAP":
state.swap((self.instruction[i][1][0], self.instruction[i][1][1]))
elif self.instruction[i][0] == "I":
pass
elif self.instruction[i][0] == "S":
state.evolve_single(Ops.s, self.instruction[i][1])
elif self.instruction[i][0] == "H":
state.evolve_single(Ops.h, self.instruction[i][1])
elif self.instruction[i][0] == "X":
state.evolve_single(Ops.x, self.instruction[i][1])
elif self.instruction[i][0] == "Y":
state.evolve_single(Ops.y, self.instruction[i][1])
elif self.instruction[i][0] == "Z":
state.evolve_single(Ops.z, self.instruction[i][1])
elif self.instruction[i][0] == "Rx":
state.evolve_single(Ops.Rx(self.instruction[i][2]), self.instruction[i][1])
elif self.instruction[i][0] == "Ry":
state.evolve_single(Ops.Ry(self.instruction[i][2]), self.instruction[i][1])
elif self.instruction[i][0] == "Rz":
state.evolve_single(Ops.Rz(self.instruction[i][2]), self.instruction[i][1])
elif self.instruction[i][0] == "Rzz":
state.evolve(Ops.Rzz(self.instruction[i][2]), [self.instruction[i][1][0], self.instruction[i][1][1]])
elif self.instruction[i][0] == "CCX":
state.evolve(Ops.ccx, [self.instruction[i][1][0], self.instruction[i][1][1], self.instruction[i][1][2]])
elif self.instruction[i][0] == "M":
_, qubit, plane, angle = self.instruction[i]
result = graphix.sim.base_backend.perform_measure(qubit, plane, angle * np.pi, state, np.random)
classical_measures.append(result)
else:
raise ValueError(f"Unknown instruction: {self.instruction[i][0]}")
return SimulateResult(state, classical_measures)