Skip to content
Open
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## [Unreleased]

### Added

- ✨ Add Iterative Quantum Phase Estimation (IQPE) benchmark ([#916]) ([**@kpassito**])

### Fixed

- 🐛 Make IQM Crystal device connectivity bidirectional ([#914]) ([**@flowerthrower**])
Expand Down
67 changes: 67 additions & 0 deletions src/mqt/bench/benchmarks/iqpe.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# 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

"""Iterative Quantum Phase Estimation benchmark definition."""

from __future__ import annotations

import math

from qiskit.circuit import ClassicalRegister, QuantumCircuit, QuantumRegister

from ._registry import register_benchmark


@register_benchmark("iqpe", description="Iterative Quantum Phase Estimation (IQPE)")
def create_circuit(num_qubits: int, num_bits: int = 3, phase: float = 0.625) -> QuantumCircuit:
"""Returns a dynamic circuit implementing Iterative Quantum Phase Estimation.

Arguments:
num_qubits: Number of qubits of the returned quantum circuit. Must be at least 2.
num_bits: Number of measured phase bits.
phase: Target phase as a fraction of one full turn. Must be in ``[0, 1)``.

Returns:
QuantumCircuit: The constructed IQPE circuit.
"""
if num_qubits < 2:
msg = "Number of qubits must be at least 2 for IQPE."
raise ValueError(msg)
if num_bits < 1:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not be the dynamic size of the benchmark (it should be num_qubits)

msg = "num_bits must be at least 1 for IQPE."
raise ValueError(msg)
if not 0 <= phase < 1:
msg = "phase must be in the interval [0, 1)."
raise ValueError(msg)

measurement = QuantumRegister(1, "measurement")
target = QuantumRegister(num_qubits - 1, "target")
phase_bits = ClassicalRegister(num_bits, "phase")
qc = QuantumCircuit(measurement, target, phase_bits, name="iqpe")

phase_per_target = phase / len(target)
for target_qubit in target:
qc.x(target_qubit)

for bit in reversed(range(num_bits)):
qc.h(measurement[0])

# Split the phase over the target register so |1...1> has the requested eigenphase.
for target_qubit in target:
qc.cp(2 * math.pi * phase_per_target * (2**bit), measurement[0], target_qubit)

for correction_bit in range(bit + 1, num_bits):
with qc.if_test((phase_bits[correction_bit], 1)):
qc.rz(-math.pi / (2 ** (correction_bit - bit)), measurement[0])

qc.h(measurement[0])
qc.measure(measurement[0], phase_bits[bit])
with qc.if_test((phase_bits[bit], 1)):
qc.x(measurement[0])

return qc
65 changes: 65 additions & 0 deletions tests/test_bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ def test_arithmetic_circuits(benchmark_name: str, input_value: int) -> None:
),
("vbe_ripple_carry_adder", 3, "unknown_adder", "kind must be 'full', 'half', or 'fixed'."),
("hhl", 2, None, "Number of qubits must be at least 3 for HHL."),
("iqpe", 1, None, "Number of qubits must be at least 2 for IQPE."),
("qpeexact", 1, None, "Number of qubits must be at least 2 for QPE exact."),
("bmw_quark_copula", 3, None, "Number of qubits must be divisible by 2."),
("ae", 1, None, r"Number of qubits must be at least 2 \(1 evaluation \+ 1 target\)."),
Expand Down Expand Up @@ -252,6 +253,70 @@ def test_graphstate_seed() -> None:
assert qc_no_seed.name == "graphstate"


def test_iqpe_circuit_structure() -> None:
"""Verify the dynamic IQPE benchmark structure."""
qc = create_circuit("iqpe", 4, num_bits=4, phase=0.625)

assert qc.name == "iqpe"
assert qc.num_qubits == 4
assert qc.num_clbits == 4
assert [reg.name for reg in qc.qregs] == ["measurement", "target"]
assert qc.qregs[1].size == 3
assert [reg.name for reg in qc.cregs] == ["phase"]

ops = qc.count_ops()
assert ops.get("x", 0) == 3
assert ops.get("h", 0) == 8
assert ops.get("cp", 0) == 12
assert ops.get("measure", 0) == 4
assert ops.get("reset", 0) == 0
assert ops.get("if_else", 0) == 10


def test_iqpe_scales_with_num_bits() -> None:
"""IQPE precision scales via classical bits and dynamic iterations."""
three_bits = create_circuit("iqpe", 3, num_bits=3)
five_bits = create_circuit("iqpe", 3, num_bits=5)

assert three_bits.num_qubits == five_bits.num_qubits == 3
assert three_bits.num_clbits == 3
assert five_bits.num_clbits == 5
assert three_bits.count_ops().get("measure", 0) == 3
assert five_bits.count_ops().get("measure", 0) == 5
assert three_bits.count_ops().get("cp", 0) == 6
assert five_bits.count_ops().get("cp", 0) == 10
assert three_bits.count_ops().get("if_else", 0) == 6
assert five_bits.count_ops().get("if_else", 0) == 15


def test_iqpe_scales_with_num_qubits() -> None:
"""IQPE circuit width scales with the requested number of qubits."""
two_qubits = create_circuit("iqpe", 2, num_bits=3)
five_qubits = create_circuit("iqpe", 5, num_bits=3)

assert two_qubits.num_qubits == 2
assert five_qubits.num_qubits == 5
assert two_qubits.num_clbits == five_qubits.num_clbits == 3
assert two_qubits.count_ops().get("x", 0) == 1
assert five_qubits.count_ops().get("x", 0) == 4
assert two_qubits.count_ops().get("cp", 0) == 3
assert five_qubits.count_ops().get("cp", 0) == 12


@pytest.mark.parametrize(
("num_bits", "phase", "msg"),
[
(0, 0.625, "num_bits must be at least 1 for IQPE."),
(3, -0.1, r"phase must be in the interval \[0, 1\)."),
(3, 1.0, r"phase must be in the interval \[0, 1\)."),
],
)
def test_iqpe_parameter_validation(num_bits: int, phase: float, msg: str) -> None:
"""Invalid IQPE parameters are rejected."""
with pytest.raises(ValueError, match=msg):
create_circuit("iqpe", 2, num_bits=num_bits, phase=phase)


# 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:
Expand Down
Loading