Skip to content

Commit

Permalink
Add benchmarking tests (Qiskit#12603)
Browse files Browse the repository at this point in the history
* added circuit functions

* updated circuits functions

* added qasm files

* added benchmarking metrics

* cleaning up circuits

* updated tests

* updated tests

* formatting

* removed unused import

* clifford synthesis circ test

* lint test
  • Loading branch information
Procatv authored Jul 19, 2024
1 parent a7e177b commit ec1f42c
Show file tree
Hide file tree
Showing 6 changed files with 265,092 additions and 2 deletions.
84 changes: 84 additions & 0 deletions test/benchmarks/circuit_construction.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@
# pylint: disable=missing-docstring,invalid-name,no-member
# pylint: disable=attribute-defined-outside-init

import os
import itertools

from qiskit.quantum_info import random_clifford
from qiskit import QuantumRegister, QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.circuit.library import EfficientSU2, QuantumVolume
from .utils import dtc_unitary, multi_control_circuit

SEED = 12345


def build_circuit(width, gates):
Expand Down Expand Up @@ -96,3 +102,81 @@ def time_bind_params(self, _, __, ___):
# TODO: write more complete benchmarks of assign_parameters
# that test more of the input formats / combinations
self.circuit.assign_parameters({x: 3.14 for x in self.params})


class ParamaterizedDifferentCircuit:
param_names = ["circuit_size", "num_qubits"]
params = ([10, 50, 100], [10, 50, 150])

def time_QV100_build(self, circuit_size, num_qubits):
"""Measures an SDKs ability to build a 100Q
QV circit from scratch.
"""
return QuantumVolume(circuit_size, num_qubits, seed=SEED)

def time_DTC100_set_build(self, circuit_size, num_qubits):
"""Measures an SDKs ability to build a set
of 100Q DTC circuits out to 100 layers of
the underlying unitary
"""
max_cycles = circuit_size
initial_state = QuantumCircuit(num_qubits)
dtc_circuit = dtc_unitary(num_qubits, g=0.95, seed=SEED)

circs = [initial_state]
for tt in range(max_cycles):
qc = circs[tt].compose(dtc_circuit)
circs.append(qc)
result = circs[-1]

return result


class MultiControl:
param_names = ["width"]
params = [10, 16, 20]

def time_multi_control_circuit(self, width):
"""Measures an SDKs ability to build a circuit
with a multi-controlled X-gate
"""
out = multi_control_circuit(width)
return out


class ParameterizedCirc:
param_names = ["num_qubits"]
params = [5, 10, 16]

def time_param_circSU2_100_build(self, num_qubits):
"""Measures an SDKs ability to build a
parameterized efficient SU2 circuit with circular entanglement
over 100Q utilizing 4 repetitions. This will yield a
circuit with 1000 parameters
"""
out = EfficientSU2(num_qubits, reps=4, entanglement="circular", flatten=True)
out._build()
return out


class QasmImport:
def time_QV100_qasm2_import(self):
"""QASM import of QV100 circuit"""
self.qasm_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "qasm"))

out = QuantumCircuit.from_qasm_file(os.path.join(self.qasm_path, "qv_N100_12345.qasm"))
ops = out.count_ops()
assert ops.get("rz", 0) == 120000
assert ops.get("rx", 0) == 80000
assert ops.get("cx", 0) == 15000
return ops


class CliffordSynthesis:
param_names = ["num_qubits"]
params = [10, 50, 100]

def time_clifford_synthesis(self, num_qubits):
cliff = random_clifford(num_qubits)
qc = cliff.to_circuit()
return qc
178 changes: 178 additions & 0 deletions test/benchmarks/manipulate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2023
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

# pylint: disable=no-member,invalid-name,missing-docstring,no-name-in-module
# pylint: disable=attribute-defined-outside-init,unsubscriptable-object
# pylint: disable=unused-wildcard-import,wildcard-import,undefined-variable

import os
import numpy as np

from qiskit import QuantumCircuit
from qiskit.converters import circuit_to_dag
from qiskit.circuit import CircuitInstruction, Qubit, library
from qiskit.dagcircuit import DAGCircuit
from qiskit.passmanager import PropertySet
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from .utils import multi_control_circuit

GATES = {
"id": library.IGate(),
"x": library.XGate(),
"y": library.YGate(),
"z": library.ZGate(),
"cx": library.CXGate(),
"cz": library.CZGate(),
}

TWIRLING_SETS_NAMES = {
"cx": [
["id", "z", "z", "z"],
["id", "x", "id", "x"],
["id", "y", "z", "y"],
["id", "id", "id", "id"],
["z", "x", "z", "x"],
["z", "y", "id", "y"],
["z", "id", "z", "id"],
["z", "z", "id", "z"],
["x", "y", "y", "z"],
["x", "id", "x", "x"],
["x", "z", "y", "y"],
["x", "x", "x", "id"],
["y", "id", "y", "x"],
["y", "z", "x", "y"],
["y", "x", "y", "id"],
["y", "y", "x", "z"],
],
"cz": [
["id", "z", "id", "z"],
["id", "x", "z", "x"],
["id", "y", "z", "y"],
["id", "id", "id", "id"],
["z", "x", "id", "x"],
["z", "y", "id", "y"],
["z", "id", "z", "id"],
["z", "z", "z", "z"],
["x", "y", "y", "x"],
["x", "id", "x", "z"],
["x", "z", "x", "id"],
["x", "x", "y", "y"],
["y", "id", "y", "z"],
["y", "z", "y", "id"],
["y", "x", "x", "y"],
["y", "y", "x", "x"],
],
}
TWIRLING_SETS = {
key: [[GATES[name] for name in twirl] for twirl in twirls]
for key, twirls in TWIRLING_SETS_NAMES.items()
}


def _dag_from_twirl(gate_2q, twirl):
dag = DAGCircuit()
# or use QuantumRegister - doesn't matter
qubits = (Qubit(), Qubit())
dag.add_qubits(qubits)
dag.apply_operation_back(twirl[0], (qubits[0],), (), check=False)
dag.apply_operation_back(twirl[1], (qubits[1],), (), check=False)
dag.apply_operation_back(gate_2q, qubits, (), check=False)
dag.apply_operation_back(twirl[2], (qubits[0],), (), check=False)
dag.apply_operation_back(twirl[3], (qubits[1],), (), check=False)
return dag


def circuit_twirl(qc, twirled_gate="cx", seed=None):
rng = np.random.default_rng(seed)
twirl_set = TWIRLING_SETS.get(twirled_gate, [])

out = qc.copy_empty_like()
for instruction in qc.data:
if instruction.operation.name != twirled_gate:
out._append(instruction)
else:
# We could also scan through `qc` outside the loop to know how many
# twirled gates we'll be dealing with, and RNG the integers ahead of
# time - that'll be faster depending on what percentage of gates are
# twirled, and how much the Numpy overhead is.
twirls = twirl_set[rng.integers(len(twirl_set))]
control, target = instruction.qubits
out._append(CircuitInstruction(twirls[0], (control,), ()))
out._append(CircuitInstruction(twirls[1], (target,), ()))
out._append(instruction)
out._append(CircuitInstruction(twirls[2], (control,), ()))
out._append(CircuitInstruction(twirls[3], (target,), ()))
return out


def dag_twirl(dag, twirled_gate="cx", seed=None):
# This mutates `dag` in place.
rng = np.random.default_rng(seed)
twirl_set = TWIRLING_DAGS.get(twirled_gate, [])
twirled_gate_op = GATES[twirled_gate].base_class

to_twirl = dag.op_nodes(twirled_gate_op)
twirl_indices = rng.integers(len(twirl_set), size=(len(to_twirl),))

for index, op_node in zip(twirl_indices, to_twirl):
dag.substitute_node_with_dag(op_node, twirl_set[index])
return dag


TWIRLING_DAGS = {
key: [_dag_from_twirl(GATES[key], twirl) for twirl in twirls]
for key, twirls in TWIRLING_SETS.items()
}


class TestCircuitManipulate:
def setup(self):
qasm_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "qasm")
self.qft_qasm = os.path.join(qasm_dir, "dtc_100_cx_12345.qasm")
self.qft_qc = QuantumCircuit.from_qasm_file(self.qft_qasm)
self.qv_qasm = os.path.join(qasm_dir, "qv_N100_12345.qasm")
self.qv_qc = QuantumCircuit.from_qasm_file(self.qv_qasm)
self.dtc_qasm = os.path.join(qasm_dir, "dtc_100_cx_12345.qasm")
self.dtc_qc = QuantumCircuit.from_qasm_file(self.dtc_qasm)
self.translate = generate_preset_pass_manager(1, basis_gates=["rx", "ry", "rz", "cz"])

def time_DTC100_twirling(self):
"""Perform Pauli-twirling on a 100Q QV
circuit
"""
out = circuit_twirl(self.dtc_qc)
return out

def time_multi_control_decompose(self):
"""Decompose a multi-control gate into the
basis [rx, ry, rz, cz]
"""
circ = multi_control_circuit(16)
self.translate.property_set = PropertySet()
out = self.translate.run(circ)
return out

def time_QV100_basis_change(self):
"""Change a QV100 circuit basis from [rx, ry, rz, cx]
to [sx, x, rz, cz]
"""
self.translate.property_set = PropertySet()
out = self.translate.run(self.qv_qc)
return out

def time_DTC100_twirling_dag(self):
"""Perform Pauli-twirling on a 100Q QV
circuit
"""
self.translate.property_set = PropertySet()
out = self.translate.run(self.qv_qc)
return circuit_to_dag(out)
Loading

0 comments on commit ec1f42c

Please sign in to comment.