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
53 changes: 53 additions & 0 deletions .agent/plans/w-state-benchmark.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# W-state preparation

Status: complete; stacked on PR #2545 and validated locally.

## Outcome and scope

The `w-state` family provides C++ `WState`, Python `mqt.core.bench.w_state`, and
the existing benchmark CLI interfaces. It prepares the equal, positive-amplitude
superposition of single-excitation states, returning every Z measurement in
`result`. Qubit zero is the least significant displayed bit. The required
positive `qubits` value must fit circuit indices and angle storage.

Generation in `mlir/bench/programs/WState.cpp` initializes qubit zero, then uses
an `scf.for` sweep of controlled RY and reverse CX. A rank-one tensor holds
precomputed angles, so jeff needs no runtime transcendental operations.

## Decisions and ownership

`src/bench/WState.cpp` provides the analytic probability: `1/n` for a
single-excitation bitstring, zero otherwise. It reuses the existing counts
metrics, JSON registry, and binding patterns. No state container, sparse input
API, or new simulator entry point is needed for this distribution evaluation.
Existing family interfaces, manifests, and case IDs remain unchanged.

Generation tests use existing QCO DD simulation and `dd::makeWState` to check
amplitudes and coherence. These implementation checks do not expand the public
benchmark API. The 4,096-qubit test checks structured generation, jeff
serialization and reload, and simulation without dense extraction. Reference
size alone does not establish a simulation-time bound for arbitrary circuits.

The QCO DD interpreter shares a 100-million-step budget across loops, branches,
and calls. It rejects resolved `scf.for` trip counts above the remaining budget
before execution; per-step accounting remains for nested flow and while loops.
Budget tests use oversized inner loops to fail promptly and retain a successful
case above the previous 10,000-step boundary. No public configuration was added.

## Validation

Run the release preset's `mqt-core-bench-test`,
`mqt-core-mlir-unittests-benchmark`, and `mqt-core-mlir-unittest-qco-utils`
binaries, plus the `mqt-core-mlir-benchmark-cli` CTest. Run the Python
benchmark, MLIR, QCO DD, and loop suites.

Regenerate stubs, build the executable documentation, and run full-file C++ lint
and repository lint as required by [AGENTS.md](../../AGENTS.md). Compare C++
lint against `origin/codex/fix-mlir-exception-boundaries`, the PR's base branch.
The docs include checked 3- and 256-qubit sampling examples; the native
regression retains the 4,096-qubit check.

Local validation passed: 66 benchmark tests, 33 generation tests, 196 QCO
utility tests, the CLI CTest, and 199 focused Python tests. Stub generation,
full-file C++ lint, repository lint, and executable documentation passed. Hosted
checks are reported separately in the PR.
1 change: 1 addition & 0 deletions bindings/bench/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ if(NOT TARGET ${MQT_CORE_TARGET_NAME}-bench-bindings)
register_bv.cpp
register_modular_multiplier.cpp
register_ghz.cpp
register_w_state.cpp
register_grover.cpp
register_multiplexer.cpp
register_qft.cpp
Expand Down
4 changes: 4 additions & 0 deletions bindings/bench/register_bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace nb = nanobind;
void registerBV(const nb::module_& m);
void registerModularMultiplier(const nb::module_& m);
void registerGHZ(const nb::module_& m);
void registerWState(const nb::module_& m);
void registerGrover(const nb::module_& m);
void registerMultiplexer(const nb::module_& m);
void registerQFT(const nb::module_& m);
Expand Down Expand Up @@ -52,6 +53,9 @@ NB_MODULE(MQT_CORE_MODULE_NAME, m) {
.def_ro("success_probability", &bench::Evaluation::successProbability,
"The observed success probability, when defined.");

registerWState(
m.def_submodule("w_state", "W-state preparation instances and options."));

const nb::module_ bv = m.def_submodule(
"bv", "Bernstein--Vazirani benchmark instances and options.");
registerBV(bv);
Expand Down
80 changes: 80 additions & 0 deletions bindings/bench/register_w_state.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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/WState.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 registerWState(const nb::module_& m) {
nb::class_<bench::WStateOptions>(m, "Options",
"Parameters for W-state preparation.")
.def(nb::init<size_t>(), nb::kw_only(), "qubits"_a)
.def_ro("qubits", &bench::WStateOptions::qubits, "The number of qubits.");

auto wState = nb::class_<bench::WState>(
m, "WState", "A validated W-state preparation benchmark.");
wState.def(nb::init<bench::WStateOptions>(), "options"_a)
.def_prop_ro("options", &bench::WState::options,
nb::rv_policy::reference_internal,
"The resolved benchmark parameters.")
.def_prop_ro("output", &bench::WState::output,
nb::rv_policy::reference_internal,
"The logical output register.")
.def("probability", &bench::WState::probability, "outcome"_a,
"Return the ideal probability of an outcome.")
.def("evaluate", &bench::WState::evaluate, "counts"_a,
"Compare sampled counts with the ideal distribution.")
.def(
"generate",
[](const bench::WState& 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::WState& value) {
return bench::toInstanceSpecificationJSON(value);
},
"The canonical instance specification JSON.")
.def_prop_ro(
"manifest_json",
[](const bench::WState& value) {
return bench::toManifestJSON(value);
},
"The canonical manifest JSON.")
.def_prop_ro(
"case_id",
[](const bench::WState& value) { return bench::caseId(value); },
"The stable semantic case ID.")
.def_static("from_instance_specification_json",
&bench::wStateFromInstanceSpecificationJSON, "json"_a,
nb::kw_only(), "source"_a = "<instance-specification>",
"Parse a strict benchmark instance specification.")
.def_static("from_manifest_json", &bench::wStateFromManifestJSON,
"json"_a, nb::kw_only(), "source"_a = "<manifest>",
"Parse a strict benchmark manifest.");
}

} // namespace mqt
39 changes: 39 additions & 0 deletions docs/benchmarks.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,45 @@ Before evaluation, normalize backend results to the manifest's big-endian

## Benchmark families

### W-state preparation

The `w-state` family prepares the equal, positive-amplitude superposition of all
single-excitation states:

```{math}
|W_n\rangle = \frac{1}{\sqrt n}\sum_{j=0}^{n-1}|2^j\rangle.
```

The required `qubits` parameter is positive. All qubits are measured in Z;
result bit $i$ is qubit $i$.

The ideal probability is $1/n$ for each single-excitation bitstring and zero
otherwise. The existing counts evaluator compares observations with this
analytic distribution:

```{code-cell} ipython3
from mqt.core import mlir
from mqt.core.bench import w_state

w = w_state.WState(w_state.Options(qubits=3))
counts = mlir.sample(w.generate(), shots=4096, seed=17)
assert set(counts) == {"001", "010", "100"}
assert w.evaluate(counts).total_variation_distance < 0.03
assert w.probability("010") == 1 / 3
```

DD sampling also supports larger instances without dense statevector extraction.
Runtime and memory depend on the intermediate DDs.

```{code-cell} ipython3
large_w = w_state.WState(w_state.Options(qubits=256))
large_program = large_w.generate()
large_counts = mlir.sample(large_program, shots=64, seed=17)
assert large_program.is_valid
assert sum(large_counts.values()) == 64
assert all(len(outcome) == 256 and outcome.count("1") == 1 for outcome in large_counts)
```

### QFT addition

The `qft-adder` family adds two equal-width operands. `REGISTER` stores the
Expand Down
4 changes: 4 additions & 0 deletions docs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,10 @@ semiclassical QFT
alias:** semiclassical QFT. A quantum Fourier-transform method that measures,
resets, and reuses one qubit for each output bit. Each round applies rotations
controlled by earlier measurement results.

W state
**Preferred term:** W state. The equal, positive-amplitude superposition of
all computational-basis states with exactly one qubit in state one.
```

## Index
Expand Down
2 changes: 2 additions & 0 deletions include/mqt-core/bench/BenchmarkFamilies.inc
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,6 @@ MQT_BENCHMARK_FAMILY(RepeatUntilSuccess, repeatUntilSuccess,
"repeat-until-success", 1)
MQT_BENCHMARK_FAMILY(Teleportation, teleportation, "teleportation", 1)

MQT_BENCHMARK_FAMILY(WState, wState, "w-state", 1)

#undef MQT_BENCHMARK_FAMILY
3 changes: 2 additions & 1 deletion include/mqt-core/bench/JSON.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "bench/QPE.hpp"
#include "bench/RepeatUntilSuccess.hpp"
#include "bench/Teleportation.hpp"
#include "bench/WState.hpp"
#include "bench/mqt_core_bench_export.h"

#include <cstddef>
Expand All @@ -33,7 +34,7 @@ namespace mqt::bench {
/// One validated benchmark instance from the JSON registry.
using BenchmarkInstance =
std::variant<BV, GHZ, Grover, ModularMultiplier, Multiplexer, QFT, QFTAdder,
QPE, RepeatUntilSuccess, Teleportation>;
QPE, RepeatUntilSuccess, Teleportation, WState>;

/// A diagnostic returned by a fallible JSON operation.
struct JSONError {
Expand Down
42 changes: 42 additions & 0 deletions include/mqt-core/bench/WState.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 W-state preparation.
struct WStateOptions {
/// Positive number of qubits; circuit dimensions must fit signed 64-bit
/// indices.
size_t qubits;
};

/// Prepare the equal, positive-amplitude superposition of single excitations.
class MQT_CORE_BENCH_EXPORT WState final {
public:
explicit WState(WStateOptions options);
[[nodiscard]] const WStateOptions& options() const noexcept;
[[nodiscard]] const Output& output() const noexcept;
[[nodiscard]] double probability(std::string_view outcome) const;
[[nodiscard]] Evaluation evaluate(const Counts& counts) const;

private:
WStateOptions options_;
Output output_;
};

} // namespace mqt::bench
3 changes: 2 additions & 1 deletion mlir/bench/programs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ add_library(
QFTUtils.cpp
QPE.cpp
RepeatUntilSuccess.cpp
Teleportation.cpp)
Teleportation.cpp
WState.cpp)
target_link_libraries(MQTBenchmarkPrograms PUBLIC MQT::CoreBench MLIRQCProgramBuilder
MLIRArithDialect MLIRTensorDialect)
mqt_mlir_target_use_project_options(MQTBenchmarkPrograms)
5 changes: 5 additions & 0 deletions mlir/bench/programs/Programs.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace mqt::bench {
class BV;
class ModularMultiplier;
class GHZ;
class WState;
class Grover;
class Multiplexer;
class QFT;
Expand All @@ -41,6 +42,10 @@ SmallVector<Value> bv(qc::QCProgramBuilder& builder, const BV& benchmark);
SmallVector<Value> modularMultiplier(qc::QCProgramBuilder& builder,
const ModularMultiplier& benchmark);

/// Emit W-state preparation.
SmallVector<Value> wState(qc::QCProgramBuilder& builder,
const WState& benchmark);

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

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

#include "mqt/Dialect/QC/Builder/QCProgramBuilder.h"

#include "Programs.h"

#include "mlir/Dialect/Arith/IR/Arith.h"
#include "mlir/Dialect/Tensor/IR/Tensor.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Value.h"
#include "mlir/IR/ValueRange.h"
#include "mlir/Support/LLVM.h"

#include "llvm/ADT/ArrayRef.h"

#include <cmath>
#include <cstddef>
#include <cstdint>
#include <vector>

namespace mqt::bench {
using namespace mlir;

SmallVector<Value> wState(qc::QCProgramBuilder& b, const WState& benchmark) {
const auto size = static_cast<int64_t>(benchmark.options().qubits);
auto q = b.allocQubitRegisterStorage(size, "q");
auto result = b.allocClassicalBitRegister(size, benchmark.output().name);
b.x(b.loadQubit(q, b.indexConstant(0)));
if (size > 1) {
std::vector<double> angles(static_cast<size_t>(size - 1));
for (size_t i = 0; i < angles.size(); ++i) {
angles[i] = 2. * std::acos(1. / std::sqrt(static_cast<double>(size) -
static_cast<double>(i)));
}
const auto type = RankedTensorType::get({size - 1}, b.getF64Type());
auto table = arith::ConstantOp::create(
b, DenseElementsAttr::get(type, ArrayRef<double>(angles)));
auto one = b.indexConstant(1);
b.scfFor(0, size - 1, 1, [&](Value index) {
auto next = arith::AddIOp::create(b, index, one);
auto left = b.loadQubit(q, index);
auto right = b.loadQubit(q, next);
auto angle = tensor::ExtractOp::create(b, table, ValueRange{index});
b.cry(angle, left, right);
b.cx(right, left);
});
}
b.measureQubitRegister(q, result, size);
return {result};
}
} // namespace mqt::bench
2 changes: 1 addition & 1 deletion mlir/include/mqt/Dialect/QCO/Utils/DDFunctionality.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ FailureOr<dd::MatrixDD> buildFunctionality(
/// In addition to the operations supported by `buildFunctionality`, simulation
/// supports measurements, resets, CBit registers, and runtime qubit and QTensor
/// allocation. QCO and SCF structured control requires concrete values. A
/// shared 10000-step limit bounds loops and calls. `qco.sink` and
/// shared 100-million-step limit bounds loops and calls. `qco.sink` and
/// `qtensor.dealloc` mark lifetimes but do not remove DD wires.
///
/// The containing module must pass MLIR verification and
Expand Down
Loading
Loading