Skip to content
This repository has been archived by the owner on Aug 19, 2023. It is now read-only.

Commit

Permalink
Add benchmarks for Sabre on large QFT and QV circuits (#1622)
Browse files Browse the repository at this point in the history
* Add benchmarks for Sabre on large QFT and QV circuits

Sabre is capable of handling these large benchmarks now, and it's of
interest for us to track our performance on large systems.  We don't
anticipate running on them yet, but we will want to know in the future
when further changes to routing and memory usage improve these
benchmarks.

* Fix lint

* Fix lint properly

* Precalculate trackers to avoid recomputation

The tracking benchmarks here naively require a recomputation of the
expensive swap-mapping, despite use wanting to just reuse things we
already calculated during the timing phase.  `asv` doesn't let us return
trackers from the timing benchmarks directly, but we can still reduce
one load of redundancy by pre-calculating all the tracker properties we
care about only once in the cached setup method, and then just feeding
that state into the actual benchmarks to retrieve the results they care
about.

This is rather hacky, but does successfully work around functionality we
would like in `asv` to reduce runtime.
  • Loading branch information
jakelishman authored Oct 28, 2022
1 parent 8c09a8f commit db24b4b
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 1 deletion.
63 changes: 62 additions & 1 deletion test/benchmarks/qft.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
# pylint: disable=missing-docstring,invalid-name,no-member
# pylint: disable=attribute-defined-outside-init

import itertools
import math

from qiskit import QuantumRegister, QuantumCircuit
from qiskit.converters import circuit_to_dag
from qiskit.transpiler import CouplingMap
from qiskit.transpiler.passes import SabreSwap
try:
from qiskit.compiler import transpile
except ImportError:
Expand All @@ -33,7 +37,9 @@ def build_model_circuit(qreg, circuit=None):

for i in range(n):
for j in range(i):
circuit.cu1(math.pi/float(2**(i-j)), qreg[i], qreg[j])
# Using negative exponents so we safely underflow to 0 rather than
# raise `OverflowError`.
circuit.cp(math.pi * (2.0 ** (j-i)), qreg[i], qreg[j])
circuit.h(qreg[i])

return circuit
Expand All @@ -56,3 +62,58 @@ def time_ibmq_backend_transpile(self, _):
basis_gates=['u1', 'u2', 'u3', 'cx', 'id'],
coupling_map=coupling_map,
seed_transpiler=20220125)


class LargeQFTMappingTimeBench:
timeout = 600.0 # seconds

heavy_hex_size = {115: 7, 409: 13, 1081: 21}
params = ([115, 409, 1081], ["lookahead", "decay"])
param_names = ["n_qubits", "heuristic"]

def setup(self, n_qubits, _heuristic):
qr = QuantumRegister(n_qubits, name="q")
self.dag = circuit_to_dag(build_model_circuit(qr))
self.coupling = CouplingMap.from_heavy_hex(
self.heavy_hex_size[n_qubits]
)

def time_sabre_swap(self, _n_qubits, heuristic):
pass_ = SabreSwap(self.coupling, heuristic, seed=2022_10_27, trials=1)
pass_.run(self.dag)


class LargeQFTMappingTrackBench:
timeout = 600.0 # seconds, needs to account for the _entire_ setup.

heavy_hex_size = {115: 7, 409: 13, 1081: 21}
params = ([115, 409, 1081], ["lookahead", "decay"])
param_names = ["n_qubits", "heuristic"]

# The benchmarks take a significant amount of time to run, and we don't
# want to unnecessarily run things twice to get the two pieces of tracking
# information we're interested in. We cheat by using the setup cache to do
# all the calculation work only once, and then each tracker just quickly
# pulls the result from the cache to return, saving the duplication.

def setup_cache(self):
def setup(n_qubits, heuristic):
qr = QuantumRegister(n_qubits, name="q")
dag = circuit_to_dag(build_model_circuit(qr))
coupling = CouplingMap.from_heavy_hex(
self.heavy_hex_size[n_qubits]
)
pass_ = SabreSwap(coupling, heuristic, seed=2022_10_27, trials=1)
return pass_.run(dag)

state = {}
for params in itertools.product(*self.params):
dag = setup(*params)
state[params] = {"depth": dag.depth(), "size": dag.size()}
return state

def track_depth_sabre_swap(self, state, *params):
return state[params]["depth"]

def track_size_sabre_swap(self, state, *params):
return state[params]["size"]
79 changes: 79 additions & 0 deletions test/benchmarks/quantum_volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,14 @@
"""Module for estimating quantum volume.
See arXiv:1811.12926 [quant-ph]"""

import itertools

import numpy as np

from qiskit.compiler import transpile
from qiskit.converters import circuit_to_dag
from qiskit.transpiler import CouplingMap
from qiskit.transpiler.passes import SabreSwap

from .utils import build_qv_model_circuit

Expand Down Expand Up @@ -54,3 +59,77 @@ def time_ibmq_backend_transpile(self, _, translation):
coupling_map=self.coupling_map,
translation_method=translation,
seed_transpiler=20220125)


class LargeQuantumVolumeMappingTimeBench:
timeout = 600.0 # seconds
heavy_hex_distance = {115: 7, 409: 13, 1081: 21}
allowed_sizes = {(115, 100), (115, 10), (409, 10), (1081, 10)}
n_qubits = sorted({n_qubits for n_qubits, _ in allowed_sizes})
depths = sorted({depth for _, depth in allowed_sizes})

params = (n_qubits, depths, ["lookahead", "decay"])
param_names = ["n_qubits", "depth", "heuristic"]

def setup(self, n_qubits, depth, _):
if (n_qubits, depth) not in self.allowed_sizes:
raise NotImplementedError
seed = 2022_10_27
self.dag = circuit_to_dag(
build_qv_model_circuit(n_qubits, depth, seed)
)
self.coupling = CouplingMap.from_heavy_hex(
self.heavy_hex_distance[n_qubits]
)

def time_sabre_swap(self, _n_qubits, _depth, heuristic):
pass_ = SabreSwap(self.coupling, heuristic, seed=2022_10_27, trials=1)
pass_.run(self.dag)


class LargeQuantumVolumeMappingTrackBench:
timeout = 600.0 # seconds

allowed_sizes = {(115, 100), (115, 10), (409, 10), (1081, 10)}
heuristics = ["lookahead", "decay"]
n_qubits = sorted({n_qubits for n_qubits, _ in allowed_sizes})
depths = sorted({depth for _, depth in allowed_sizes})

params = (n_qubits, depths, heuristics)
param_names = ["n_qubits", "depth", "heuristic"]

# The benchmarks take a significant amount of time to run, and we don't
# want to unnecessarily run things twice to get the two pieces of tracking
# information we're interested in. We cheat by using the setup cache to do
# all the calculation work only once, and then each tracker just quickly
# pulls the result from the cache to return, saving the duplication.

def setup_cache(self):
heavy_hex_distance = {115: 7, 409: 13, 1081: 21}
seed = 2022_10_27

def setup(n_qubits, depth, heuristic):
dag = circuit_to_dag(
build_qv_model_circuit(n_qubits, depth, seed)
)
coupling = CouplingMap.from_heavy_hex(heavy_hex_distance[n_qubits])
return SabreSwap(coupling, heuristic, seed=seed, trials=1).run(dag)

state = {}
for params in itertools.product(*self.params):
n_qubits, depth, _ = params
if (n_qubits, depth) not in self.allowed_sizes:
continue
dag = setup(*params)
state[params] = {"depth": dag.depth(), "size": dag.size()}
return state

def setup(self, _state, n_qubits, depth, _heuristic):
if (n_qubits, depth) not in self.allowed_sizes:
raise NotImplementedError

def track_depth_sabre_swap(self, state, *params):
return state[params]["depth"]

def track_size_sabre_swap(self, state, *params):
return state[params]["size"]

0 comments on commit db24b4b

Please sign in to comment.