Note
Click here to download the full example code
Statevector simulation of MBQC patterns
Here we benchmark our statevector simulator for MBQC.
- The methods and modules we use are the followings:
graphix.pattern.Pattern.simulate_pattern()Pattern simulator with statevector backend.
paddle_quantum.mbqcPattern simulation using
paddle_quantum.mbqc.
Firstly, let us import relevant modules:
from time import perf_counter
import matplotlib.pyplot as plt
import numpy as np
from graphix import Circuit
from paddle import to_tensor
from paddle_quantum.mbqc.qobject import Circuit as PaddleCircuit
from paddle_quantum.mbqc.transpiler import transpile as PaddleTranspile
from paddle_quantum.mbqc.simulator import MBQC as PaddleMBQC
/home/docs/checkouts/readthedocs.org/user_builds/graphix/envs/v0.2.9/lib/python3.10/site-packages/setuptools/sandbox.py:13: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
import pkg_resources
/home/docs/checkouts/readthedocs.org/user_builds/graphix/envs/v0.2.9/lib/python3.10/site-packages/pkg_resources/__init__.py:2868: DeprecationWarning: Deprecated call to `pkg_resources.declare_namespace('google')`.
Implementing implicit namespace packages (as specified in PEP 420) is preferred to `pkg_resources.declare_namespace`. See https://setuptools.pypa.io/en/latest/references/keywords.html#keyword-namespace-packages
declare_namespace(pkg)
/home/docs/checkouts/readthedocs.org/user_builds/graphix/envs/v0.2.9/lib/python3.10/site-packages/pkg_resources/__init__.py:2868: DeprecationWarning: Deprecated call to `pkg_resources.declare_namespace('sphinxcontrib')`.
Implementing implicit namespace packages (as specified in PEP 420) is preferred to `pkg_resources.declare_namespace`. See https://setuptools.pypa.io/en/latest/references/keywords.html#keyword-namespace-packages
declare_namespace(pkg)
Next, define a circuit to be transpiled into measurement pattern:
def simple_random_circuit(nqubit, depth):
r"""Generate a test circuit for benchmarking.
This function generates a circuit with nqubit qubits and depth layers,
having layers of CNOT and Rz gates with random placements.
Parameters
----------
nqubit : int
number of qubits
depth : int
number of layers
Returns
-------
circuit : graphix.transpiler.Circuit object
generated circuit
"""
qubit_index = [i for i in range(nqubit)]
circuit = Circuit(nqubit)
for _ in range(depth):
np.random.shuffle(qubit_index)
for j in range(len(qubit_index) // 2):
circuit.cnot(qubit_index[2 * j], qubit_index[2 * j + 1])
for j in range(len(qubit_index)):
circuit.rz(qubit_index[j], 2 * np.pi * np.random.random())
return circuit
We define the test cases: shallow (depth=1) random circuits, only changing the number of qubits.
DEPTH = 1
test_cases = [i for i in range(2, 22)]
graphix_circuits = {}
pattern_time = []
circuit_time = []
We then run simulations. First, we run the pattern simulation using graphix. For reference, we perform simple statevector simulation of the original gate network. Since transpilation into MBQC involves a significant increase in qubit number, the MBQC simulation is inherently slower as we will see.
for width in test_cases:
circuit = simple_random_circuit(width, DEPTH)
graphix_circuits[width] = circuit
pattern = circuit.transpile()
pattern.standardize()
pattern.minimize_space()
nodes, edges = pattern.get_graph()
nqubit = len(nodes)
start = perf_counter()
pattern.simulate_pattern(max_qubit_num=30)
end = perf_counter()
print(f"width: {width}, nqubit: {nqubit}, depth: {DEPTH}, time: {end - start}")
pattern_time.append(end - start)
start = perf_counter()
circuit.simulate_statevector()
end = perf_counter()
circuit_time.append(end - start)
width: 2, nqubit: 8, depth: 1, time: 0.0033045050004147924
width: 3, nqubit: 11, depth: 1, time: 0.004107420999389433
width: 4, nqubit: 16, depth: 1, time: 0.006145097000626265
width: 5, nqubit: 19, depth: 1, time: 0.0076387419994716765
width: 6, nqubit: 24, depth: 1, time: 0.009847833000094397
width: 7, nqubit: 27, depth: 1, time: 0.011233553000238317
width: 8, nqubit: 32, depth: 1, time: 0.013807816999360512
width: 9, nqubit: 35, depth: 1, time: 0.01550606099954166
width: 10, nqubit: 40, depth: 1, time: 0.019187212999895564
width: 11, nqubit: 43, depth: 1, time: 0.02222777200040582
width: 12, nqubit: 48, depth: 1, time: 0.0298761909998575
width: 13, nqubit: 51, depth: 1, time: 0.06923882199953368
width: 14, nqubit: 56, depth: 1, time: 0.12748390500019013
width: 15, nqubit: 59, depth: 1, time: 0.21143460700022843
width: 16, nqubit: 64, depth: 1, time: 0.40970752900011576
width: 17, nqubit: 67, depth: 1, time: 0.7184588470008748
width: 18, nqubit: 72, depth: 1, time: 2.6574321469997813
width: 19, nqubit: 75, depth: 1, time: 4.686827492999328
width: 20, nqubit: 80, depth: 1, time: 12.33061879700017
width: 21, nqubit: 83, depth: 1, time: 24.394883134999873
Here we benchmark paddle_quantum, using the same original gate network and use paddle_quantum.mbqc module to transpile into a measurement pattern.
def translate_graphix_rc_into_paddle_quantum_circuit(graphix_circuit: Circuit) -> PaddleCircuit:
"""Translate graphix circuit into paddle_quantum circuit.
Parameters
----------
graphix_circuit : Circuit
graphix circuit
Returns
-------
paddle_quantum_circuit : PaddleCircuit
paddle_quantum circuit
"""
paddle_quantum_circuit = PaddleCircuit(graphix_circuit.width)
for instr in graphix_circuit.instruction:
if instr[0] == "CNOT":
paddle_quantum_circuit.cnot(which_qubits=instr[1])
elif instr[0] == "Rz":
paddle_quantum_circuit.rz(which_qubit=instr[1], theta=to_tensor(instr[2], dtype="float64"))
return paddle_quantum_circuit
test_cases_for_paddle_quantum = [i for i in range(2, 22)]
paddle_quantum_time = []
for width in test_cases_for_paddle_quantum:
graphix_circuit = graphix_circuits[width]
paddle_quantum_circuit = translate_graphix_rc_into_paddle_quantum_circuit(graphix_circuit)
pat = PaddleTranspile(paddle_quantum_circuit)
mbqc = PaddleMBQC()
mbqc.set_pattern(pat)
start = perf_counter()
mbqc.run_pattern()
end = perf_counter()
paddle_quantum_time.append(end - start)
print(f"width: {width}, depth: {DEPTH}, time: {end - start}")
width: 2, depth: 1, time: 0.026631699000063236
width: 3, depth: 1, time: 0.030918536999706703
width: 4, depth: 1, time: 0.029844511000192142
width: 5, depth: 1, time: 0.028311512999607658
width: 6, depth: 1, time: 0.04087219200027903
width: 7, depth: 1, time: 0.045209016000626434
width: 8, depth: 1, time: 0.05366268200032209
width: 9, depth: 1, time: 0.06281454600048164
width: 10, depth: 1, time: 0.06779313100014406
width: 11, depth: 1, time: 0.07832329799930449
width: 12, depth: 1, time: 0.09675737600082357
width: 13, depth: 1, time: 0.12402491099965118
width: 14, depth: 1, time: 0.1507354289997238
width: 15, depth: 1, time: 0.22181575800004794
width: 16, depth: 1, time: 0.5156858649997957
width: 17, depth: 1, time: 0.9126530930006993
width: 18, depth: 1, time: 1.2093880069996885
width: 19, depth: 1, time: 2.8149927710001066
width: 20, depth: 1, time: 5.284183010000561
width: 21, depth: 1, time: 16.170350954999776
Lastly, we compare the simulation times.
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(
test_cases, circuit_time, label="direct statevector sim of original gate-based circuit (reference)", marker="x"
)
ax.scatter(test_cases, pattern_time, label="graphix pattern simulator")
ax.scatter(test_cases_for_paddle_quantum, paddle_quantum_time, label="paddle_quantum pattern simulator")
ax.set(
xlabel="Width of the original circuit",
ylabel="time (s)",
yscale="log",
title="Time to simulate random circuits",
)
fig.legend(bbox_to_anchor=(0.85, 0.9))
fig.show()

MBQC simulation is a lot slower than the simulation of original gate network, since the number of qubit involved is significantly larger.
import importlib.metadata # noqa: E402
# print package versions.
[
print("{} - {}".format(pkg, importlib.metadata.version(pkg)))
for pkg in ["numpy", "graphix", "paddlepaddle", "paddle-quantum"]
]
numpy - 1.23.5
graphix - 0.2.9
paddlepaddle - 2.4.0rc0
paddle-quantum - 2.2.0
[None, None, None, None]
Total running time of the script: ( 1 minutes 19.320 seconds)