✨ Add typed structured quantum benchmarks - #2135
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
33b6b18 to
a8f62c2
Compare
Assisted-by: Claude Opus 5 via Claude Code
Assisted-by: Claude Opus 5 via Claude Code
Assisted-by: Claude Opus 5 via Claude Code
Assisted-by: Claude Opus 5 via Claude Code
Assisted-by: Claude Opus 5 via Claude Code
Assisted-by: Claude Opus 5 via Claude Code
Assisted-by: Claude Opus 5 via Claude Code
Assisted-by: Opus 5 via Claude Code
Assisted-by: Opus 5 via Claude Code
Assisted-by: Opus 5 via Claude Code
Assisted-by: Opus 5 via Claude Code
Assisted-by: Opus 5 via Claude Code
Assisted-by: Opus 5 via Claude Code
Assisted-by: Opus 5 via Claude Code
c0882d7 to
2d99355
Compare
|
Modulo some more testing, we should be able to mark this PR as fixing #1115. |
Assisted-by: Opus 5 via Claude Code
Assisted-by: Opus 5 via Claude Code
jeff benchmark programsjeff generator
Assisted-by: Opus 5 via Claude Code
Assisted-by: Opus 5 via Claude Code
Assisted-by: OpenAI Codex
jeff generatorRename the unreleased public component to bench, add typed Bernstein--Vazirani and QFT families, centralize private dispatch, simplify generation, and make the notebook executable. Preserve Daniel Haag’s original structured-program commits in the branch history while keeping new contracts and architecture in this commit. Assisted-by: Codex
Process the no-swap Fourier circuit from the most significant physical qubit and apply semiclassical feed-forward in the same order. Assisted-by: Codex (cherry picked from commit abd9ac38fcf3b7068588895e1bf16eb6725a6943)
Apply the repository formatter to the new BV and Fourier sources and their semantic tests. Assisted-by: Codex
Record combined-stack, installation, wheel, documentation, test, and lint results, including the unrelated QDMI Python blocker. Assisted-by: Codex
DRovara
left a comment
There was a problem hiding this comment.
This is really cool! I didn't expect to find an entire benchmark suite with actual evaluations here when I opened the PR but this is pretty nice.
In fact, looking ahead, I can really imagine adding further custom benchmarks to this to e.g. create a reproduction environment for paper results, but that's beyond the scope for now.
I guess the one disadvantage I see here is that it seems like adding another benchmark program requires quite some effort, but I guess there could be worse things.
The programs themselves look correct, I didn't notice any issues there.
| MQT Core defines a benchmark as validated, benchmark-specific parameters plus an | ||
| analytic reference. One instance produces a structured QC program, a resolved | ||
| manifest, and a stable case ID. Each benchmark returns one classical register | ||
| named `result`. Outcome strings are big-endian: the highest-index result bit is | ||
| the leftmost character. |
There was a problem hiding this comment.
I feel like this is a bit difficult to understand, especially considering the loaded term "benchmark".
If I understand it correctly, how about a rewording like:
Benchmarks in MQT Core are defines as a tuple of:
- benchmark-specific parameters
- an analytic reference
They allow the construction of:
- A structured QC program
- a resolved manifest
- a stable case ID
Resulting programs return one classical register named [...]
For instance, from the current description, I still wouldn't understand that we are talking about specific programs here. And since your first sentence defines a benchmark as parameters + analytic reference, the sentence "each benchmark returns [...]" feels a bit weird - how can parameters + analytic reference even return anything?
There was a problem hiding this comment.
Yeah. I like the direction of your suggestion! I'll take that into the next iteration!
| ```{code-cell} ipython3 | ||
| import json | ||
| import subprocess | ||
|
|
||
|
|
||
| def run_bench(*arguments: str) -> dict[str, object]: | ||
| """Run mqt-core-bench and parse its JSON output.""" | ||
| completed = subprocess.run( | ||
| ["mqt-core-bench", *arguments], | ||
| check=True, | ||
| capture_output=True, | ||
| text=True, | ||
| ) | ||
| return json.loads(completed.stdout) | ||
|
|
||
|
|
||
| catalog = run_bench("list") | ||
| [entry["id"] for entry in catalog["benchmarks"]] | ||
| ``` | ||
|
|
||
| ```{code-cell} ipython3 | ||
| qft_schema = run_bench("describe", "qft") | ||
| qft_schema["properties"]["parameters"] | ||
| ``` |
There was a problem hiding this comment.
Isn't it a bit weird to illustrate example usages of a CLI tool with python code and subprocess? This makes it seem like subprocess is the exected way to run it...
There was a problem hiding this comment.
Yeah. It certainly is; especially since there is Python code for doing this.
I suppose this was mostly about showing how the CLI can be used, but there are better ways to do that in Jupyter Notebooks. I'll revisit this.
| namespace mqt::bench { | ||
|
|
||
| /// Circuit method used for the quantum Fourier transform. | ||
| enum class QFTMethod : uint8_t { Standard, Semiclassical }; |
There was a problem hiding this comment.
I just realised that (I think) there is no documentation anywhere on what these options mean. Here, I didn't quite get what "Semiclassical" means. Is it the same iterative approach as the iQPE?
There was a problem hiding this comment.
No, these should probably be documented inline with Doxygen comments.
Same idea as iQPE. semiclassical is just the established name from the literature.
| return {result}; | ||
| } | ||
|
|
||
| auto query = builder.allocQubitRegisterStorage(width, "query"); |
There was a problem hiding this comment.
This is just a quick random thought but would it make sense/how much effort would it be to also provide an option that can be used for all benchmark programs that replaces any and all qubit registers with multiple individually allocated single qubits?
There was a problem hiding this comment.
Hm. I see the idea. Feels like this would be a bit complicated though. Especially for the algorithms using loops because they currently index into registers in the loops, which is not directly possible if the program just has loose qubits.
At the top of my head, I don't see a very easy solution. But maybe I am also overlooking things.
Clarify the instance/program boundary and document the dynamic Fourier methods in the executable notebook. Align the feature with current repository lint and guidance after the main merge. Assisted-by: Codex
| } | ||
| } | ||
|
|
||
| b.scfFor(0, search, 1, [&](Value iv) { b.h(b.loadQubit(q.value, iv)); }); |
There was a problem hiding this comment.
Seeing this, I think it could make sense to have an abstraction that "applies" single qubit gates to registers.
There was a problem hiding this comment.
One could even think about allowing registers as arguments of the gates and defining broadcast rules similar to OpenQASM. But I am not quite sure we want this, because it adds a whole bunch of complexity.
What should be easy is adding convenience methods in the builders for easing the construction of the construct here.
There was a problem hiding this comment.
I think a modifier along the lines
%out:9 = qco.broadcast %in:9 (%target) { qco.h(%target) } : (!qco.qubit, ...., !qco.qubit)
%tensor_out = qco.broadcast %tensor_in (%target) { qco.x(%target) } : (!tensor.tensor<!qco.qubit>)could be kind of nice as this doesn't add overhead to the individual gates. But definitely not a must have right now.
There was a problem hiding this comment.
There may be a precedent in MLIR for how to handle this. This is kind of like vectorization; and there is a vector dialect in MLIR. Might be worth to brainstorm these ideas with an LLM and collect the thoughts in an issue.
This si nothing to be addressed in this PR.
There was a problem hiding this comment.
Thanks a lot for all the improvements, @burgholzer! I really like this a lot! 🙂
I went through the PR pretty carefully now and left a comment wherever I stumbled. I'll feed these comments into Codex in a bit. I'm not sure about some of my comments, which is why I sometimes explicitly tagged you.
Nice! I think I commented on every tag. Let me know if I missed something! |
Assisted-by: GPT-5.6 Sol via Codex
Assisted-by: GPT-5.6 Sol via Codex
Call the resolved configuration an instance and define its generated manifest as the semantic sidecar used for evaluation. Assisted-by: GPT-5.6 Sol via Codex
Keep each emitter self-contained, use lowercase MLIR paths, and add recurring constant operations to the program builder. Apply the remaining C++ review cleanups in the same structural batch. Assisted-by: GPT-5.6 Sol via Codex
Make notebook output easier to read, hide setup details, and link the generated program API. Cover the cross-platform benchmark launcher directly so Python patch coverage includes it. Assisted-by: GPT-5.6 Sol via Codex
Assisted-by: GPT-5.6 Sol via Codex
denialhaag
left a comment
There was a problem hiding this comment.
Another set of comments:
Name the registry iterator type explicitly instead of assuming that std::array iterators are pointers. MSVC uses a wrapper iterator type. Assisted-by: GPT-5.6 Sol via Codex
Group benchmark CLI tests after the core CLI cases. Use QFT and QPE consistently, update constant helper comments to current Doxygen style, and keep the benchmark guide independent of its current families. Assisted-by: GPT-5.6 Sol via Codex
Thanks a lot for the quick input, @burgholzer! 🙂 I might have gotten a bit lost in the details here, but I think I'm happy with this now. I'm kind of indifferent about adding more benchmarks now or in one or more follow-ups. The latter would certainly make reviewing easier, but only if we leave the infrastructure (largely) alone. Either way, we should add most (if not all) structured benchmarks mentioned in the |
Assisted-by: GPT-5.6 Sol via Codex
|
I'll try to get a review in for this tomorrow, so that we can get this merged and not hold it up for too long. Follow-ups should come quite naturally. |
Description
🤖 AI text below 🤖
Add a typed foundation for structured quantum benchmarks.
The new MLIR-free
MQT::CoreBenchlibrary owns each benchmark's parameters, validation, logical output, analytic reference, and evaluation. This vertical slice contains:Strict, versioned JSON instances materialize all defaults. Self-checking manifests record the resolved parameters, output convention, reference model, and a deterministic case ID. The C++, Python, and
mqt-core-benchAPIs expose the same contract. The command-line tool lists and describes benchmarks, generates structured QC orjeff, and evaluates counts against a manifest.The MLIR layer contains compact, structured QC emitters for the five benchmark families. It consumes typed instances and keeps its private dispatch table aligned with the semantic registry instead of duplicating defaults. The Python
mqt.core.benchmodule delegates program generation to the existing MLIR extension, which keeps one MLIR runtime per process.Distribution-level execution tests against the DD simulator remain a follow-up on the stack that starts at #2077.
Fixes #1115
AI notice
This PR and its contents were created with the assistance of Opus 5 via Claude Code and GPT-5.6 Sol via Codex.
Checklist
If PR contains AI-assisted content:
🤖 *AI text below* 🤖(titles are exempt).