Skip to content
Draft
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
50 changes: 50 additions & 0 deletions .agent/plans/qft-adder-quantum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Add a quantum-input QFT adder benchmark

Status: in progress. The implementation is validated. The draft pull request and
its changelog reference remain to be added.

## Goal and scope

Add the `qft-adder-quantum` structured benchmark from Draper's
[Addition on a Quantum Computer](https://arxiv.org/abs/quant-ph/0008033). The
benchmark must be available through the typed C++, JSON, command-line, Python,
and MLIR generation interfaces. It must generate the full no-swap QFT, Draper
addition, and inverse-QFT circuit rather than a circuit with the same output
distribution.

The benchmark parameter is the width `n` of each quantum register. The source
register is prepared as |+>^n and the accumulator as |1>. The one logical
`result` output has width `2n` and is written as the big-endian concatenation
`addend || sum`. Its ideal distribution has probability `2^-n` exactly when
`sum = addend + 1 mod 2^n`. Measuring both registers keeps this correlation
observable; measuring the sum alone would produce an uninformative uniform
distribution.

## Decisions

Register index zero is the least-significant bit. The forward QFT uses no swaps
and visits targets from most to least significant. For target `t`, it applies H
and then `CP(pi / 2^(t-c))` from every lower control `c`. The addition block
applies the same controlled-phase gate from source control `c <= t` to
accumulator target `t`, including each `CP(pi)` gate. The inverse QFT reverses
the complete gate order and negates each phase. `CP` cannot be replaced with a
controlled RZ because their relative phases differ.

The width is limited to 1024 qubits per register. This keeps the smallest
required binary phase and the ideal probability representable as `double`. The
implementation does not add swaps, carry qubits, approximate rotations, or an
alternative QFT convention. A private MLIR helper may own the shared forward and
inverse no-swap transforms; it must not change the existing QFT benchmark.

## Work remaining

- [ ] Create the draft stacked pull request and fold its number into the
existing unreleased structured-benchmark changelog entry.

## Validation

The release build, all 50 native benchmark tests, all 15 MLIR benchmark tests,
the benchmark CLI test, and 23 focused Python benchmark and CLI tests pass. The
Python test samples the width-three circuit and compares the result with the
analytic correlation. Stub generation, the general repository lint session, and
`git diff --check` pass. The separate C++ lint session was not run.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ releases may include breaking changes.
- ✨ Add a library for typed structured quantum benchmarks with versioned
instance specifications, analytic references, deterministic manifests, and
C++, Python, and command-line interfaces ([#2135], [#2299], [#2315], [#2324],
[#2337], [#2380], [#2402]) ([**@burgholzer**], [**@denialhaag**])
[#2337], [#2380], [#2402], [#2404]) ([**@burgholzer**], [**@denialhaag**])
- ✨ Add DD construction, simulation, statevector extraction, and sampling for
QCO programs with structured control and dynamic quantum data, including
direct lowering and dense-array helpers for supported compiler inputs
Expand Down Expand Up @@ -882,6 +882,7 @@ for previous changelogs._

<!-- PR links -->

[#2404]: https://github.com/munich-quantum-toolkit/core/pull/2404
[#2402]: https://github.com/munich-quantum-toolkit/core/pull/2402
[#2380]: https://github.com/munich-quantum-toolkit/core/pull/2380
[#2368]: https://github.com/munich-quantum-toolkit/core/pull/2368
Expand Down
1 change: 1 addition & 0 deletions bindings/bench/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ if(NOT TARGET ${MQT_CORE_TARGET_NAME}-bench-bindings)
register_grover.cpp
register_multiplexer.cpp
register_qft.cpp
register_qft_adder_quantum.cpp
register_qpe.cpp
register_teleportation.cpp)

Expand Down
5 changes: 5 additions & 0 deletions bindings/bench/register_bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ void registerGHZ(const nb::module_& m);
void registerGrover(const nb::module_& m);
void registerMultiplexer(const nb::module_& m);
void registerQFT(const nb::module_& m);
void registerQFTAdderQuantum(const nb::module_& m);
void registerQPE(const nb::module_& m);
void registerTeleportation(const nb::module_& m);

Expand Down Expand Up @@ -69,6 +70,10 @@ NB_MODULE(MQT_CORE_MODULE_NAME, m) {
m.def_submodule("qft", "QFT benchmark instances and options.");
registerQFT(qft);

const nb::module_ qftAdderQuantum = m.def_submodule(
"qft_adder_quantum", "Quantum-input QFT adder instances and options.");
registerQFTAdderQuantum(qftAdderQuantum);

const nb::module_ qpe =
m.def_submodule("qpe", "QPE benchmark instances and options.");
registerQPE(qpe);
Expand Down
85 changes: 85 additions & 0 deletions bindings/bench/register_qft_adder_quantum.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* 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
*/

#include "bench/JSON.hpp"
#include "bench/QFTAdderQuantum.hpp"

#include <nanobind/nanobind.h>
#include <nanobind/stl/map.h> // NOLINT(misc-include-cleaner)
#include <nanobind/stl/string.h> // NOLINT(misc-include-cleaner)
#include <nanobind/stl/string_view.h> // NOLINT(misc-include-cleaner)

#include <cstddef>

namespace mqt {

namespace nb = nanobind;
using namespace nb::literals;

// NOLINTNEXTLINE(misc-use-internal-linkage)
void registerQFTAdderQuantum(const nb::module_& m) {
nb::class_<bench::QFTAdderQuantumOptions>(
m, "Options", "Parameters for a quantum-input QFT adder benchmark.")
.def(nb::init<size_t>(), nb::kw_only(), "qubits"_a)
.def_ro("qubits", &bench::QFTAdderQuantumOptions::qubits,
"The number of qubits in each input register.");

auto qftAdder = nb::class_<bench::QFTAdderQuantum>(
m, "QFTAdderQuantum", "A validated quantum-input QFT adder benchmark.");
qftAdder.def(nb::init<bench::QFTAdderQuantumOptions>(), "options"_a)
.def_prop_ro("options", &bench::QFTAdderQuantum::options,
nb::rv_policy::reference_internal,
"The resolved benchmark parameters.")
.def_prop_ro(
"output", &bench::QFTAdderQuantum::output,
nb::rv_policy::reference_internal,
"The logical output register, with the addend followed by the sum.")
.def("probability", &bench::QFTAdderQuantum::probability, "outcome"_a,
"Return the ideal probability of an outcome.")
.def("evaluate", &bench::QFTAdderQuantum::evaluate, "counts"_a,
"Compare sampled counts with the ideal distribution.")
.def(
"generate",
[](const bench::QFTAdderQuantum& value) {
return nb::module_::import_("mqt.core.mlir")
.attr("_generate_benchmark")(
bench::toInstanceSpecificationJSON(value));
},
nb::sig("def generate(self) -> mqt.core.mlir.QCProgram"),
"Generate the benchmark as a QC program.")
.def_prop_ro(
"instance_specification_json",
[](const bench::QFTAdderQuantum& value) {
return bench::toInstanceSpecificationJSON(value);
},
"The canonical instance specification JSON.")
.def_prop_ro(
"manifest_json",
[](const bench::QFTAdderQuantum& value) {
return bench::toManifestJSON(value);
},
"The canonical manifest JSON.")
.def_prop_ro(
"case_id",
[](const bench::QFTAdderQuantum& value) {
return bench::caseId(value);
},
"The stable semantic case ID.")
.def_static("from_instance_specification_json",
&bench::qftAdderQuantumFromInstanceSpecificationJSON,
"json"_a, nb::kw_only(),
"source"_a = "<instance-specification>",
"Parse a strict benchmark instance specification.")
.def_static("from_manifest_json", &bench::qftAdderQuantumFromManifestJSON,
"json"_a, nb::kw_only(), "source"_a = "<manifest>",
"Parse a strict benchmark manifest.");
}

} // namespace mqt
14 changes: 14 additions & 0 deletions docs/benchmarks.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ print("Width:", benchmark.output.width)
Each family validates its instance when it creates one. Fixed families need no
options.

## Quantum-input QFT adder

The `qft-adder-quantum` family implements Draper's
[QFT adder](https://arxiv.org/abs/quant-ph/0008033). For a configured width `n`,
the benchmark prepares an `n`-qubit addend register in the uniform
superposition and an `n`-qubit accumulator in state |1>. It applies the exact
no-swap QFT to the accumulator, the complete controlled-phase addition, and the
inverse QFT.

The `2n`-bit result is the big-endian concatenation `addend || sum`. An outcome
has probability `2^-n` when `sum = addend + 1 mod 2^n` and probability zero
otherwise. Keeping both registers in the result exposes the correlation that
defines the addition; the sum alone would be uniform.

## Inspect the canonical instance specification and manifest

A canonical instance specification records every resolved default. A manifest
Expand Down
1 change: 1 addition & 0 deletions include/mqt-core/bench/BenchmarkFamilies.inc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ MQT_BENCHMARK_FAMILY(GHZ, ghz, "ghz", 1)
MQT_BENCHMARK_FAMILY(Grover, grover, "grover", 1)
MQT_BENCHMARK_FAMILY(Multiplexer, multiplexer, "multiplexer", 1)
MQT_BENCHMARK_FAMILY(QFT, qft, "qft", 1)
MQT_BENCHMARK_FAMILY(QFTAdderQuantum, qftAdderQuantum, "qft-adder-quantum", 1)
MQT_BENCHMARK_FAMILY(QPE, qpe, "qpe", 1)
MQT_BENCHMARK_FAMILY(Teleportation, teleportation, "teleportation", 1)

Expand Down
1 change: 1 addition & 0 deletions include/mqt-core/bench/JSON.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "bench/Grover.hpp"
#include "bench/Multiplexer.hpp"
#include "bench/QFT.hpp"
#include "bench/QFTAdderQuantum.hpp"
#include "bench/QPE.hpp"
#include "bench/Teleportation.hpp"
#include "bench/mqt_core_bench_export.h"
Expand Down
46 changes: 46 additions & 0 deletions include/mqt-core/bench/QFTAdderQuantum.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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
*/

#pragma once

#include "bench/Evaluation.hpp"
#include "bench/mqt_core_bench_export.h"

#include <cstddef>
#include <string_view>

namespace mqt::bench {

/// Parameters for one quantum-input QFT adder benchmark instance.
struct QFTAdderQuantumOptions {
static constexpr size_t MAX_QUBITS = 1'024;

/// Number of qubits in each input register.
size_t qubits;
};

/// A validated quantum-input QFT adder and its analytic reference.
class MQT_CORE_BENCH_EXPORT QFTAdderQuantum final {
public:
explicit QFTAdderQuantum(QFTAdderQuantumOptions options);

[[nodiscard]] const QFTAdderQuantumOptions& options() const noexcept;
[[nodiscard]] const Output& output() const noexcept;
/// Return the ideal probability of a big-endian logical outcome.
[[nodiscard]] double probability(std::string_view outcome) const;
/// Compare sampled logical outcomes with the ideal distribution.
[[nodiscard]] Evaluation evaluate(const Counts& counts) const;

private:
QFTAdderQuantumOptions options_;
Output output_;
};

} // namespace mqt::bench
2 changes: 2 additions & 0 deletions mlir/bench/programs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ add_library(
Grover.cpp
Multiplexer.cpp
QFT.cpp
QFTAdderQuantum.cpp
QFTAdderUtils.cpp
QPE.cpp
Teleportation.cpp)
target_link_libraries(MQTBenchmarkPrograms PUBLIC MQT::CoreBench MLIRQCProgramBuilder
Expand Down
5 changes: 5 additions & 0 deletions mlir/bench/programs/Programs.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class GHZ;
class Grover;
class Multiplexer;
class QFT;
class QFTAdderQuantum;
class QPE;
class Teleportation;
} // namespace mqt::bench
Expand All @@ -48,6 +49,10 @@ SmallVector<Value> multiplexer(qc::QCProgramBuilder& builder,
/// Emit one configured QFT benchmark.
SmallVector<Value> qft(qc::QCProgramBuilder& builder, const QFT& benchmark);

/// Emit one configured quantum-input QFT adder benchmark.
SmallVector<Value> qftAdderQuantum(qc::QCProgramBuilder& builder,
const QFTAdderQuantum& benchmark);

/// Emit one configured QPE benchmark.
SmallVector<Value> qpe(qc::QCProgramBuilder& builder, const QPE& benchmark);

Expand Down
86 changes: 86 additions & 0 deletions mlir/bench/programs/QFTAdderQuantum.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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
*/

#include "bench/QFTAdderQuantum.hpp"

#include "Programs.h"
#include "QFTAdderUtils.h"
#include "mlir/Dialect/QC/Builder/QCProgramBuilder.h"

#include <mlir/Dialect/Arith/IR/Arith.h>
#include <mlir/Dialect/SCF/IR/SCF.h>
#include <mlir/IR/Builders.h>
#include <mlir/IR/Value.h>
#include <mlir/IR/ValueRange.h>
#include <mlir/Support/LLVM.h>

#include <cstdint>
#include <numbers>

namespace mqt::bench {

using namespace mlir;

static void addQuantumRegister(qc::QCProgramBuilder& builder, Value addend,
Value sum, int64_t qubits) {
auto zero = builder.indexConstant(0);
auto one = builder.indexConstant(1);
auto last = builder.indexConstant(qubits - 1);
builder.scfFor(0, qubits, 1, [&](Value step) {
auto target = arith::SubIOp::create(builder, last, step).getResult();
auto upper = arith::AddIOp::create(builder, target, one).getResult();
auto firstAngle = builder.floatConstant(std::numbers::pi);
auto half = builder.floatConstant(0.5);
auto loop =
scf::ForOp::create(builder, zero, upper, one, ValueRange{firstAngle});
OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToStart(loop.getBody());
auto angle = loop.getRegionIterArg(0);
auto control =
arith::SubIOp::create(builder, target, loop.getInductionVar())
.getResult();
builder.cp(angle, builder.loadQubit(addend, control),
builder.loadQubit(sum, target));
auto next = arith::MulFOp::create(builder, angle, half).getResult();
scf::YieldOp::create(builder, ValueRange{next});
});
}

SmallVector<Value> qftAdderQuantum(qc::QCProgramBuilder& builder,
const QFTAdderQuantum& benchmark) {
const auto qubits = static_cast<int64_t>(benchmark.options().qubits);
auto addend = builder.allocQubitRegisterStorage(qubits, "addend");
auto sum = builder.allocQubitRegisterStorage(qubits, "sum");
auto result = builder.allocClassicalBitRegister(
static_cast<int64_t>(benchmark.output().width), benchmark.output().name);

builder.scfFor(0, qubits, 1, [&](Value index) {
builder.h(builder.loadQubit(addend, index));
});
auto zero = builder.indexConstant(0);
builder.x(builder.loadQubit(sum, zero));

detail::forwardQFT(builder, sum, qubits);
addQuantumRegister(builder, addend, sum, qubits);
detail::inverseQFT(builder, sum, qubits);

builder.scfFor(0, qubits, 1, [&](Value index) {
builder.measure(builder.loadQubit(sum, index), result, index);
});
auto resultOffset = builder.indexConstant(qubits);
builder.scfFor(0, qubits, 1, [&](Value index) {
auto resultIndex =
arith::AddIOp::create(builder, resultOffset, index).getResult();
builder.measure(builder.loadQubit(addend, index), result, resultIndex);
});
return {result};
}

} // namespace mqt::bench
Loading
Loading