diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d1e6a495..46c9aa7a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Added +- ✨ Add Dynamic QFT benchmark ([#798]) ([**@kris70lesgo**]) - ✨ Add Iterative Quantum Phase Estimation (IQPE) benchmark ([#925]) ([**@johanneswittmann9**]) ### Fixed @@ -130,6 +131,7 @@ _📚 Refer to the [GitHub Release Notes] for previous changelogs._ +[#798]: https://github.com/munich-quantum-toolkit/bench/issues/798 [#925]: https://github.com/munich-quantum-toolkit/bench/pull/925 [#914]: https://github.com/munich-quantum-toolkit/bench/pull/914 [#895]: https://github.com/munich-quantum-toolkit/bench/pull/895 @@ -187,6 +189,7 @@ _📚 Refer to the [GitHub Release Notes] for previous changelogs._ [**@TomasVF**]: https://github.com/TomasVF [**@flowerthrower**]: https://github.com/flowerthrower [**@johanneswittmann9**]: https://github.com/johanneswittmann9 +[**@kris70lesgo**]: https://github.com/kris70lesgo diff --git a/src/mqt/bench/benchmarks/dynamic_qft.py b/src/mqt/bench/benchmarks/dynamic_qft.py new file mode 100644 index 000000000..1224ee901 --- /dev/null +++ b/src/mqt/bench/benchmarks/dynamic_qft.py @@ -0,0 +1,50 @@ +# 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 + + +@register_benchmark("dynamic_qft", description="Dynamic QFT") +def create_circuit(num_qubits: int) -> QuantumCircuit: + """Returns a quantum circuit implementing the dynamic Quantum Fourier Transform. + + The circuit implements the semiclassical QFT followed by measurement from Griffiths and Niu + and the dynamic QFT construction described in Phys. Rev. Lett. 133, 150602 (2024). It mirrors + the operation order of the regular QFT benchmark, but replaces controlled phase gates from + already measured qubits by classically controlled phase rotations. + + Arguments: + num_qubits: number of qubits of the returned quantum circuit. + + Returns: + QuantumCircuit: a quantum circuit implementing the dynamic Quantum Fourier Transform. + """ + q = QuantumRegister(num_qubits, "q") + c = ClassicalRegister(num_qubits, "c") + qc = QuantumCircuit(q, c, name="dynamic_qft") + + for qubit in reversed(range(num_qubits)): + measured_bit = num_qubits - qubit - 1 + + for control_qubit in range(num_qubits - 1, qubit, -1): + control_bit = num_qubits - control_qubit - 1 + with qc.if_test((c[control_bit], 1)): + qc.p(math.pi / (1 << (control_qubit - qubit)), q[qubit]) + + qc.h(q[qubit]) + qc.measure(q[qubit], c[measured_bit]) + + return qc diff --git a/tests/test_bench.py b/tests/test_bench.py index 0ee4eea9f..7b7c75a8b 100644 --- a/tests/test_bench.py +++ b/tests/test_bench.py @@ -267,6 +267,81 @@ def test_graphstate_seed() -> None: assert qc_no_seed.name == "graphstate" +# Test the dynamic circuits +def test_dynamic_qft_circuit_structure() -> None: + """Verify the structure of the Dynamic QFT benchmark.""" + num_qubits = 5 + qc = create_circuit("dynamic_qft", num_qubits) + + assert qc.name == "dynamic_qft" + assert qc.num_qubits == num_qubits + assert qc.num_clbits == num_qubits + assert len(qc.qregs) == 1 + assert qc.qregs[0].name == "q" + assert qc.qregs[0].size == num_qubits + assert len(qc.cregs) == 1 + assert qc.cregs[0].name == "c" + assert qc.cregs[0].size == num_qubits + + ops = qc.count_ops() + assert ops.get("h", 0) == num_qubits + assert ops.get("measure", 0) == num_qubits + assert ops.get("if_else", 0) == num_qubits * (num_qubits - 1) // 2 + assert ops.get("cp", 0) == 0 + + measurements = [ + (qc.find_bit(instruction.qubits[0]).index, qc.find_bit(instruction.clbits[0]).index) + for instruction in qc.data + if instruction.operation.name == "measure" + ] + assert measurements == [(qubit, num_qubits - qubit - 1) for qubit in reversed(range(num_qubits))] + + +@pytest.mark.parametrize("num_qubits", [1, 3, 4]) +def test_dynamic_qft_matches_qft_operation_order(num_qubits: int) -> None: + """Verify that Dynamic QFT mirrors QFT phase and Hadamard ordering.""" + qft = create_circuit("qft", num_qubits).decompose() + dynamic_qft = create_circuit("dynamic_qft", num_qubits) + + qft_ops = [ + ( + instruction.operation.name, + tuple(qft.find_bit(qubit).index for qubit in instruction.qubits), + tuple(round(float(parameter), 12) for parameter in instruction.operation.params), + ) + for instruction in qft.data + if instruction.operation.name in {"cp", "h"} + ] + + dynamic_ops = [] + for instruction in dynamic_qft.data: + operation = instruction.operation + if isinstance(operation, IfElseOp): + condition_bit = dynamic_qft.find_bit(instruction.clbits[0]).index + conditional_instruction = operation.blocks[0].data[0] + dynamic_ops.append(( + "cp", + ( + num_qubits - condition_bit - 1, + dynamic_qft.find_bit(conditional_instruction.qubits[0]).index, + ), + (round(float(conditional_instruction.operation.params[0]), 12),), + )) + elif operation.name == "h": + dynamic_ops.append(("h", (dynamic_qft.find_bit(instruction.qubits[0]).index,), ())) + + assert dynamic_ops == qft_ops + + +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.num_clbits == 65 + assert qc.count_ops().get("measure", 0) == 65 + + # Test the dynamic GHZ circuit @pytest.mark.parametrize("num_qubits", [1, 2, 3, 7, 10]) def test_dynamic_ghz_circuit_structure(num_qubits: int) -> None: