Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🎨 Improved SupermarQ feature calculation #298

Merged
merged 7 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 37 additions & 44 deletions src/mqt/bench/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import numpy as np
from pytket import __version__ as __tket_version__
from qiskit import QuantumCircuit, __qiskit_version__
from qiskit.converters import circuit_to_dag
from qiskit_optimization.applications import Maxcut

from mqt.bench.devices import OQCProvider
Expand Down Expand Up @@ -242,50 +243,42 @@ def calc_qubit_index(qargs: list[Qubit], qregs: list[QuantumRegister], index: in
def calc_supermarq_features(
qc: QuantumCircuit,
) -> SupermarqFeatures:
connectivity_collection: list[list[int]] = []
liveness_A_matrix = 0
connectivity_collection = [[] for _ in range(qc.num_qubits)]

for instruction, qargs, _ in qc.data:
if instruction.name in ("barrier", "measure"):
continue
liveness_A_matrix += len(qargs)
first_qubit = calc_qubit_index(qargs, qc.qregs, 0)
all_indices = [first_qubit]
if len(qargs) == 2:
second_qubit = calc_qubit_index(qargs, qc.qregs, 1)
all_indices.append(second_qubit)
for qubit_index in all_indices:
to_be_added_entries = all_indices.copy()
to_be_added_entries.remove(int(qubit_index))
connectivity_collection[int(qubit_index)].extend(to_be_added_entries)

connectivity: list[int] = [len(set(connectivity_collection[i])) for i in range(qc.num_qubits)]

count_ops = qc.count_ops()
num_gates = sum(count_ops.values())
# before subtracting the measure and barrier gates, check whether it is in the dict
if "measure" in count_ops:
num_gates -= count_ops.get("measure")
if "barrier" in count_ops:
num_gates -= count_ops.get("barrier")
num_multiple_qubit_gates = qc.num_nonlocal_gates()
depth = qc.depth(lambda x: x[0].name not in ("barrier", "measure"))
program_communication = np.sum(connectivity) / (qc.num_qubits * (qc.num_qubits - 1))

if num_multiple_qubit_gates == 0:
critical_depth = 0.0
else:
critical_depth = (
qc.depth(filter_function=lambda x: len(x[1]) > 1 and x[0].name != "barrier") / num_multiple_qubit_gates
)

entanglement_ratio = num_multiple_qubit_gates / num_gates
assert num_multiple_qubit_gates <= num_gates

parallelism = (num_gates / depth - 1) / (qc.num_qubits - 1)

liveness = liveness_A_matrix / (depth * qc.num_qubits)
"""Calculates the Supermarq features for a given quantum circuit. Code adapted from https://github.com/Infleqtion/client-superstaq/blob/91d947f8cc1d99f90dca58df5248d9016e4a5345/supermarq-benchmarks/supermarq/converters.py"""
num_qubits = qc.num_qubits
dag = circuit_to_dag(qc)
dag.remove_all_ops_named("barrier")

# Program communication = circuit's average qubit degree / degree of a complete graph.
graph = nx.Graph()
for op in dag.two_qubit_ops():
q1, q2 = op.qargs
graph.add_edge(qc.find_bit(q1).index, qc.find_bit(q2).index)
degree_sum = sum([graph.degree(n) for n in graph.nodes])
program_communication = degree_sum / (num_qubits * (num_qubits - 1)) if num_qubits > 1 else 0

# Liveness feature = sum of all entries in the liveness matrix / (num_qubits * depth).
activity_matrix = np.zeros((num_qubits, dag.depth()))
for i, layer in enumerate(dag.layers()):
for op in layer["partition"]:
for qubit in op:
activity_matrix[qc.find_bit(qubit).index, i] = 1
liveness = np.sum(activity_matrix) / (num_qubits * dag.depth()) if dag.depth() > 0 else 0

# Parallelism feature = max((((# of gates / depth) -1) /(# of qubits -1)), 0).
parallelism = (
max(((len(dag.gate_nodes()) / dag.depth()) - 1) / (num_qubits - 1), 0)
if num_qubits > 1 and dag.depth() > 0
else 0
)

# Entanglement-ratio = ratio between # of 2-qubit gates and total number of gates in the circuit.
entanglement_ratio = len(dag.two_qubit_ops()) / len(dag.gate_nodes()) if len(dag.gate_nodes()) > 0 else 0

# Critical depth = # of 2-qubit gates along the critical path / total # of 2-qubit gates.
longest_paths = dag.count_ops_longest_path()
n_ed = sum([longest_paths[name] for name in {op.name for op in dag.two_qubit_ops()} if name in longest_paths])
n_e = len(dag.two_qubit_ops())
critical_depth = n_ed / n_e if n_e != 0 else 0

assert 0 <= program_communication <= 1
assert 0 <= critical_depth <= 1
Expand Down
32 changes: 29 additions & 3 deletions tests/test_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -1059,9 +1059,35 @@ def test_count_qubit_numbers_per_compiler(compiler: str, expected_val: list[int]


def test_calc_supermarq_features() -> None:
qc = get_benchmark("dj", 1, 5)
features = utils.calc_supermarq_features(qc)
assert type(features) == utils.SupermarqFeatures
ghz_qc = get_benchmark("ghz", 1, 5)
ghz_features = utils.calc_supermarq_features(ghz_qc)
assert ghz_features.program_communication == 0.4
assert ghz_features.entanglement_ratio == 0.8
assert ghz_features.critical_depth == 1.0
assert ghz_features.parallelism == 0.0

empty_qc = QuantumCircuit(2)
empty_features = utils.calc_supermarq_features(empty_qc)
assert empty_features.parallelism == 0.0
assert empty_features.entanglement_ratio == 0.0
assert empty_features.critical_depth == 0.0
assert empty_features.program_communication == 0.0

dense_qc = QuantumCircuit(2)
dense_qc.h([0, 1])
dense_features = utils.calc_supermarq_features(dense_qc)
assert dense_features.parallelism == 1.0
assert dense_features.entanglement_ratio == 0.0
assert dense_features.critical_depth == 0.0
assert dense_features.program_communication == 0.0

regular_qc = get_benchmark("vqe", 1, 5)
regular_features = utils.calc_supermarq_features(regular_qc)
assert 0 < regular_features.parallelism < 1
assert 0 < regular_features.entanglement_ratio < 1
assert 0 < regular_features.critical_depth < 1
assert 0 < regular_features.program_communication < 1
assert 0 < regular_features.liveness < 1


def test_BenchmarkGenerator() -> None:
Expand Down
Loading