From 8d20f9da43477321fad9dba258d023a7b072ead9 Mon Sep 17 00:00:00 2001 From: gshaowei6 <47922975+gshaowei6@users.noreply.github.com> Date: Sun, 7 Jun 2026 05:55:28 +0800 Subject: [PATCH 1/3] feat: add dynamic qft benchmark Assisted-by: GPT-5 via Codex --- src/mqt/bench/benchmarks/dynamic_qft.py | 58 +++++++++++++++++++++++++ tests/test_bench.py | 51 +++++++++++++++++++++- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/mqt/bench/benchmarks/dynamic_qft.py diff --git a/src/mqt/bench/benchmarks/dynamic_qft.py b/src/mqt/bench/benchmarks/dynamic_qft.py new file mode 100644 index 000000000..f7e75407f --- /dev/null +++ b/src/mqt/bench/benchmarks/dynamic_qft.py @@ -0,0 +1,58 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Dynamic QFT benchmark definition.""" + +from __future__ import annotations + +import math + +from qiskit.circuit import ClassicalRegister, QuantumCircuit, QuantumRegister + +from ._registry import register_benchmark + +_MAX_NUM_QUBITS = 64 + + +@register_benchmark("dynamic_qft", description="Dynamic QFT") +def create_circuit(num_qubits: int) -> QuantumCircuit: + """Return a circuit implementing the Dynamic QFT. + + This benchmark follows the semiclassical Quantum Fourier Transform by + Griffiths and Niu. Each qubit is transformed with a Hadamard gate, measured + immediately, and then its classical result controls phase rotations on the + remaining qubits. This replaces the usual controlled-phase gates with + mid-circuit measurements and classical feed-forward while keeping the same + triangular phase-angle structure as the standard QFT. + + Arguments: + num_qubits: number of qubits of the returned quantum circuit. Must be at most 64. + + Returns: + QuantumCircuit: a quantum circuit implementing the Dynamic QFT. + + Raises: + ValueError: if more than 64 qubits are requested. + """ + if num_qubits > _MAX_NUM_QUBITS: + msg = "Dynamic QFT supports at most 64 qubits." + raise ValueError(msg) + + q = QuantumRegister(num_qubits, "q") + c = ClassicalRegister(num_qubits, "c") + qc = QuantumCircuit(q, c, name="dynamic_qft") + + for measured_qubit in range(num_qubits): + qc.h(q[measured_qubit]) + qc.measure(q[measured_qubit], c[measured_qubit]) + + for offset in range(1, num_qubits - measured_qubit): + with qc.if_test((c[measured_qubit], 1)): + qc.p(math.pi / (2**offset), q[measured_qubit + offset]) + + return qc diff --git a/tests/test_bench.py b/tests/test_bench.py index 850fef287..d193dd317 100644 --- a/tests/test_bench.py +++ b/tests/test_bench.py @@ -14,6 +14,7 @@ import datetime import functools import io +import math import re from enum import Enum from importlib import metadata @@ -252,7 +253,55 @@ def test_graphstate_seed() -> None: assert qc_no_seed.name == "graphstate" -# Test the dynamic GHZ circuit +# Test the dynamic circuits +def _conditional_phase_angles(qc: QuantumCircuit) -> list[float]: + """Return phase angles applied inside conditional blocks.""" + angles = [] + for instruction in qc.data: + operation = instruction.operation + if isinstance(operation, IfElseOp): + angles.extend( + float(conditional_instruction.operation.params[0]) + for conditional_instruction in operation.blocks[0].data + if conditional_instruction.operation.name == "p" + ) + return angles + + +def test_dynamic_qft_description() -> None: + """Verify that the Dynamic QFT benchmark is registered with its canonical name.""" + description = get_benchmark_description("dynamic_qft") + assert "Dynamic QFT" in description + assert "Dynamical" not in description + + +def test_dynamic_qft_circuit_structure() -> None: + """Verify the structure of the Dynamic QFT benchmark.""" + qc = create_circuit("dynamic_qft", 5) + + assert qc.name == "dynamic_qft" + assert qc.num_qubits == 5 + assert len(qc.cregs) == 1 + assert qc.cregs[0].name == "c" + assert qc.cregs[0].size == 5 + + ops = qc.count_ops() + assert ops.get("h", 0) == 5 + assert ops.get("measure", 0) == 5 + assert ops.get("if_else", 0) == 10 + assert ops.get("cp", 0) == 0 + + expected_angles = sorted(round(math.pi / (2**shift), 12) for shift in range(1, 5) for _ in range(5 - shift)) + actual_angles = sorted(round(angle, 12) for angle in _conditional_phase_angles(qc)) + assert actual_angles == expected_angles + + +def test_dynamic_qft_rejects_too_many_qubits() -> None: + """Verify that Dynamic QFT rejects instances with impractically small phase shifts.""" + with pytest.raises(ValueError, match="at most 64 qubits"): + create_circuit("dynamic_qft", 65) + + @pytest.mark.parametrize("num_qubits", [1, 2, 3, 7, 10]) def test_dynamic_ghz_circuit_structure(num_qubits: int) -> None: """Verify the structure of the dynamic GHZ circuit for various qubit counts.""" From 336e71c76e183df1e0dc3d38fc08041987407a3e Mon Sep 17 00:00:00 2001 From: gshaowei6 <47922975+gshaowei6@users.noreply.github.com> Date: Sun, 7 Jun 2026 05:59:16 +0800 Subject: [PATCH 2/3] docs: add dynamic qft changelog entry Assisted-by: GPT-5 via Codex --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 991a365b4..4278d3aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Added + +- Add Dynamic QFT benchmark ([#918]) ([**@gshaowei6**]) + ### Fixed - 🐛 Make IQM Crystal device connectivity bidirectional ([#914]) ([**@flowerthrower**]) @@ -126,6 +130,7 @@ _📚 Refer to the [GitHub Release Notes] for previous changelogs._ +[#918]: https://github.com/munich-quantum-toolkit/bench/pull/918 [#914]: https://github.com/munich-quantum-toolkit/bench/pull/914 [#895]: https://github.com/munich-quantum-toolkit/bench/pull/895 [#865]: https://github.com/munich-quantum-toolkit/bench/pull/865 @@ -171,6 +176,7 @@ _📚 Refer to the [GitHub Release Notes] for previous changelogs._ [**@burgholzer**]: https://github.com/burgholzer +[**@gshaowei6**]: https://github.com/gshaowei6 [**@ystade**]: https://github.com/ystade [**@simon1hofmann**]: https://github.com/simon1hofmann [**@nquetschlich**]: https://github.com/nquetschlich From b8d3ff15c00f24823a65bb514e7964fcf2c4a52d Mon Sep 17 00:00:00 2001 From: gshaowei6 <47922975+gshaowei6@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:24:01 +0800 Subject: [PATCH 3/3] Address Dynamic QFT review feedback --- CHANGELOG.md | 2 +- src/mqt/bench/benchmarks/dynamic_qft.py | 16 ++++------------ tests/test_bench.py | 16 ++++++++++++---- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4278d3aba..419cb5e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added -- Add Dynamic QFT benchmark ([#918]) ([**@gshaowei6**]) +- ✨ Add Dynamic QFT benchmark ([#918]) ([**@gshaowei6**]) ### Fixed diff --git a/src/mqt/bench/benchmarks/dynamic_qft.py b/src/mqt/bench/benchmarks/dynamic_qft.py index f7e75407f..4a0de9250 100644 --- a/src/mqt/bench/benchmarks/dynamic_qft.py +++ b/src/mqt/bench/benchmarks/dynamic_qft.py @@ -16,8 +16,6 @@ from ._registry import register_benchmark -_MAX_NUM_QUBITS = 64 - @register_benchmark("dynamic_qft", description="Dynamic QFT") def create_circuit(num_qubits: int) -> QuantumCircuit: @@ -31,28 +29,22 @@ def create_circuit(num_qubits: int) -> QuantumCircuit: triangular phase-angle structure as the standard QFT. Arguments: - num_qubits: number of qubits of the returned quantum circuit. Must be at most 64. + num_qubits: number of qubits of the returned quantum circuit. Returns: QuantumCircuit: a quantum circuit implementing the Dynamic QFT. - - Raises: - ValueError: if more than 64 qubits are requested. """ - if num_qubits > _MAX_NUM_QUBITS: - msg = "Dynamic QFT supports at most 64 qubits." - raise ValueError(msg) - q = QuantumRegister(num_qubits, "q") c = ClassicalRegister(num_qubits, "c") qc = QuantumCircuit(q, c, name="dynamic_qft") for measured_qubit in range(num_qubits): + measured_bit = num_qubits - measured_qubit - 1 qc.h(q[measured_qubit]) - qc.measure(q[measured_qubit], c[measured_qubit]) + qc.measure(q[measured_qubit], c[measured_bit]) for offset in range(1, num_qubits - measured_qubit): - with qc.if_test((c[measured_qubit], 1)): + with qc.if_test((c[measured_bit], 1)): qc.p(math.pi / (2**offset), q[measured_qubit + offset]) return qc diff --git a/tests/test_bench.py b/tests/test_bench.py index d193dd317..614cfa514 100644 --- a/tests/test_bench.py +++ b/tests/test_bench.py @@ -290,16 +290,24 @@ def test_dynamic_qft_circuit_structure() -> None: assert ops.get("measure", 0) == 5 assert ops.get("if_else", 0) == 10 assert ops.get("cp", 0) == 0 + assert [ + (qc.find_bit(instruction.qubits[0]).index, qc.find_bit(instruction.clbits[0]).index) + for instruction in qc.data + if instruction.operation.name == "measure" + ] == [(qubit, 4 - qubit) for qubit in range(5)] expected_angles = sorted(round(math.pi / (2**shift), 12) for shift in range(1, 5) for _ in range(5 - shift)) actual_angles = sorted(round(angle, 12) for angle in _conditional_phase_angles(qc)) assert actual_angles == expected_angles -def test_dynamic_qft_rejects_too_many_qubits() -> None: - """Verify that Dynamic QFT rejects instances with impractically small phase shifts.""" - with pytest.raises(ValueError, match="at most 64 qubits"): - create_circuit("dynamic_qft", 65) +def test_dynamic_qft_supports_large_instances() -> None: + """Verify that Dynamic QFT supports arbitrary benchmark sizes like the regular QFT.""" + qc = create_circuit("dynamic_qft", 65) + + assert qc.num_qubits == 65 + assert qc.cregs[0].size == 65 + assert qc.count_ops().get("measure", 0) == 65 @pytest.mark.parametrize("num_qubits", [1, 2, 3, 7, 10])