Statevector simulation of MBQC patterns

Here we benchmark our statevector simulator for MBQC.

The methods and modules we use are the followings:
  1. graphix.pattern.Pattern.simulate_pattern()

    Pattern simulator with statevector backend.

  2. paddle_quantum.mbqc

    Pattern 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()
Time to simulate random circuits

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)

Gallery generated by Sphinx-Gallery