Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
84afe8f
Add a `numTwoQubitGates` method to `QCProgram`
denialhaag Aug 18, 2026
0c4b0cd
Move `numTwoQubitGates` below the conversion methods
denialhaag Aug 18, 2026
b703bdf
Merge remote-tracking branch 'origin/main' into mlir-count-two-qubit-…
denialhaag Aug 18, 2026
938db92
Merge remote-tracking branch 'origin/main' into mlir-count-two-qubit-…
denialhaag Aug 22, 2026
b959f56
Simplify implementation
denialhaag Aug 22, 2026
5730fc6
Add `numSingleQubitGates` method
denialhaag Aug 22, 2026
9270fee
Add `numGates` method
denialhaag Aug 23, 2026
3889c7e
Improve docstrings
denialhaag Aug 23, 2026
90dda74
Share counting logic
denialhaag Aug 23, 2026
ca64202
Count gates in structured control flow
denialhaag Aug 24, 2026
19bb45d
Fix linter error
denialhaag Aug 24, 2026
2036631
Merge remote-tracking branch 'origin/main' into mlir-count-two-qubit-…
denialhaag Aug 24, 2026
5edd9f5
Merge remote-tracking branch 'origin/main' into mlir-count-two-qubit-…
denialhaag Aug 25, 2026
76ace93
Clean up a bit
denialhaag Aug 25, 2026
6bdbeea
Document gate-counting semantics
denialhaag Aug 25, 2026
5178a24
Merge remote-tracking branch 'origin/main' into mlir-count-two-qubit-…
denialhaag Aug 26, 2026
882354e
Document QC gate inspection methods
denialhaag Aug 26, 2026
2b61d1f
Use typed MLIR walk for gate counts
burgholzer Aug 26, 2026
976d420
Deduplicate gate count tests
burgholzer Aug 26, 2026
0a9bf43
Merge remote-tracking branch 'origin/main' into mlir-count-two-qubit-…
denialhaag Aug 26, 2026
e315516
Update changelog
denialhaag Aug 26, 2026
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
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ releases may include breaking changes.
[#1807], [#1808], [#1815], [#1824], [#1869], [#1872], [#1914], [#1925],
[#1927], [#1935], [#1936], [#1938], [#1975], [#1976], [#2006], [#2014],
[#2015], [#2017], [#2026], [#2028], [#2054], [#2058], [#2125], [#2136],
[#2150], [#2158], [#2210], [#2211]) ([**@burgholzer**], [**@denialhaag**],
[**@taminob**], [**@DRovara**], [**@li-mingbao**], [**@Ectras**],
[**@MatthiasReumann**], [**@simon1hofmann**], [**@J4MMlE**])
[#2149], [#2150], [#2158], [#2210], [#2211]) ([**@burgholzer**],
[**@denialhaag**], [**@taminob**], [**@DRovara**], [**@li-mingbao**],
[**@Ectras**], [**@MatthiasReumann**], [**@simon1hofmann**], [**@J4MMlE**])
- ✨ Add decision diagram-based construction, simulation, and sampling for QCO
programs ([#1915], [#1973]) ([**@simon1hofmann**])
- ✨ Add immutable MLIR compiler targets, QDMI device integration, and target
Expand Down Expand Up @@ -863,6 +863,7 @@ for previous changelogs._
[#2156]: https://github.com/munich-quantum-toolkit/core/pull/2156
[#2154]: https://github.com/munich-quantum-toolkit/core/pull/2154
[#2150]: https://github.com/munich-quantum-toolkit/core/pull/2150
[#2149]: https://github.com/munich-quantum-toolkit/core/pull/2149
[#2148]: https://github.com/munich-quantum-toolkit/core/pull/2148
[#2147]: https://github.com/munich-quantum-toolkit/core/pull/2147
[#2141]: https://github.com/munich-quantum-toolkit/core/pull/2141
Expand Down
38 changes: 37 additions & 1 deletion bindings/mlir/register_mlir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,43 @@ Set ``copy=True`` to preserve it.)pb")
"profile"_a, nb::kw_only(), "copy"_a = false,
R"pb(Lower this program to QIR for the requested profile.

Set ``copy=True`` to preserve it.)pb");
Set ``copy=True`` to preserve it.)pb")
.def(
"num_gates",
[](const mlir::QCProgram& program) {
requireValid(program);
return program.numGates();
},
R"pb(Count the gates in the program.

Any operation that implements the ``UnitaryOpInterface`` is counted. Operations
in every structured control-flow region are counted once, regardless of how
often the region executes. Operations within modifiers are not counted
recursively, and barriers are skipped.)pb")
.def(
"num_single_qubit_gates",
[](const mlir::QCProgram& program) {
requireValid(program);
return program.numSingleQubitGates();
},
R"pb(Count the single-qubit gates in the program.

Any operation that implements the ``UnitaryOpInterface`` and acts on one qubit
is counted. Operations in every structured control-flow region are counted
once, regardless of how often the region executes. Operations within modifiers
are not counted recursively, and barriers are skipped.)pb")
.def(
"num_two_qubit_gates",
[](const mlir::QCProgram& program) {
requireValid(program);
return program.numTwoQubitGates();
},
R"pb(Count the two-qubit gates in the program.

Any operation that implements the ``UnitaryOpInterface`` and acts on two qubits
is counted. Operations in every structured control-flow region are counted
once, regardless of how often the region executes. Operations within modifiers
are not counted recursively, and barriers are skipped.)pb");

auto qcoProgram = nb::class_<mlir::QCOProgram, mlir::Program>(
m, "QCOProgram", R"pb(A compiler program in the QCO dialect.
Expand Down
15 changes: 15 additions & 0 deletions docs/mlir/python_compiler_collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ constructing MLIR directly, return the values produced by the measurement
operations.
:::

## Inspect a QC program

Use the inspection methods of a {py:class}`~mqt.core.mlir.QCProgram` to count
gates without parsing the textual IR:

```{code-cell} ipython3
print("Gates:", compiled.num_gates())
print("Single-qubit gates:", compiled.num_single_qubit_gates())
print("Two-qubit gates:", compiled.num_two_qubit_gates())
```

These counts describe the entry-point IR. A gate in each structured control-flow
region counts once, regardless of the runtime path or loop iteration count.
Barriers do not count, and operations inside gate modifiers do not count again.

## Select an output format

Select an output format to stop the pipeline at a particular representation:
Expand Down
32 changes: 32 additions & 0 deletions mlir/include/mlir/Compiler/Programs.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,38 @@ class QCProgram final : public Program {

/// Consume this program and lower it to QIR.
[[nodiscard]] std::optional<QIRProgram> intoQIR(QIRProfile profile) &&;

/**
* @brief Count the gates in the program.
*
* @details Any operation that implements the `UnitaryOpInterface` is counted.
* The count includes operations in every structured control-flow region once,
* regardless of how often the region executes. Operations within modifiers
* are not counted recursively, and barriers are skipped.
*/
[[nodiscard]] size_t numGates() const;

/**
* @brief Count the single-qubit gates in the program.
*
* @details Any operation that implements the `UnitaryOpInterface` and acts on
* one qubit is counted. The count includes operations in every structured
* control-flow region once, regardless of how often the region executes.
* Operations within modifiers are not counted recursively, and barriers are
* skipped.
*/
[[nodiscard]] size_t numSingleQubitGates() const;

/**
* @brief Count the two-qubit gates in the program.
*
* @details Any operation that implements the `UnitaryOpInterface` and acts on
* two qubits is counted. The count includes operations in every structured
* control-flow region once, regardless of how often the region executes.
* Operations within modifiers are not counted recursively, and barriers are
* skipped.
*/
[[nodiscard]] size_t numTwoQubitGates() const;
};

/**
Expand Down
30 changes: 30 additions & 0 deletions mlir/lib/Compiler/Programs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#include "mlir/Dialect/MQT/Transforms/GlobalPhaseNormalization.h"
#include "mlir/Dialect/MQT/Transforms/Passes.h"
#include "mlir/Dialect/QC/IR/QCDialect.h"
#include "mlir/Dialect/QC/IR/QCInterfaces.h"
#include "mlir/Dialect/QC/IR/QCOps.h"
#include "mlir/Dialect/QC/Translation/TranslateQASM3ToQC.h"
#include "mlir/Dialect/QC/Translation/TranslateQCToOpenQASM3.h"
#include "mlir/Dialect/QCO/IR/QCODialect.h"
Expand Down Expand Up @@ -56,6 +58,7 @@
#include <mlir/IR/Location.h>
#include <mlir/IR/OwningOpRef.h>
#include <mlir/IR/Verifier.h>
#include <mlir/IR/Visitors.h>
#include <mlir/Parser/Parser.h>
#include <mlir/Pass/PassManager.h>
#include <mlir/Support/FileUtilities.h>
Expand Down Expand Up @@ -373,6 +376,33 @@ std::optional<QIRProgram> QCProgram::intoQIR(const QIRProfile profile) && {
return result;
}

static size_t
countGatesIf(ModuleOp moduleOp,
const llvm::function_ref<bool(qc::UnitaryOpInterface)> predicate) {
size_t count = 0;
auto entryPoint = mqt::getEntryPoint(moduleOp);
entryPoint.walk<WalkOrder::PreOrder>([&](qc::UnitaryOpInterface op) {
count += !isa<qc::BarrierOp>(op) && predicate(op);
return isa<qc::CtrlOp, qc::InvOp, qc::PowOp>(op) ? WalkResult::skip()
: WalkResult::advance();
});
return count;
}

size_t QCProgram::numGates() const {
return countGatesIf(mod(), [](qc::UnitaryOpInterface) { return true; });
}

size_t QCProgram::numSingleQubitGates() const {
return countGatesIf(
mod(), [](qc::UnitaryOpInterface op) { return op.isSingleQubit(); });
}

size_t QCProgram::numTwoQubitGates() const {
return countGatesIf(
mod(), [](qc::UnitaryOpInterface op) { return op.isTwoQubit(); });
}

//===----------------------------------------------------------------------===//
// QCOProgram
//===----------------------------------------------------------------------===//
Expand Down
60 changes: 59 additions & 1 deletion mlir/unittests/Compiler/test_compiler_pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,8 @@ TEST(CompilerProgramOwnershipTest, ValidatesAndOwnsExistingQCModules) {
EXPECT_FALSE(
QCProgram::fromModule(otherContext, std::move(mismatchedModule)));
}
/** @brief Raw QCO stops before the registered default optimization pipeline. */

/** @brief Raw QCO stops before the registered default optimization pipeline. */
TEST_F(CompilerPipelineTest, RawAndOptimizedQCOAreDistinctCheckpoints) {
const std::string qasm = R"(OPENQASM 3.0;
include "stdgates.inc";
Expand Down Expand Up @@ -1589,4 +1589,62 @@ INSTANTIATE_TEST_SUITE_P(
MQT_NAMED_BUILDER(mlir::qir::singleControlledXOnIndividualQubits),
true, "reuse-qubits,mqt-qco-default"}));

/**
* @brief Test: gate counting respects modifiers and skips barriers.
*/
TEST_F(CompilerPipelineTest, QCProgramCountGates) {
const std::string qasm = R"(OPENQASM 3.0;
include "stdgates.inc";
qubit[3] q;
h q[0];
cx q[0], q[1];
barrier q[0];
swap q[0], q[1];
ccx q[0], q[1], q[2];
ctrl @ swap q[0], q[1], q[2];
inv @ cx q[0], q[1];
barrier q[0], q[1];
)";
auto qc = QCProgram::fromQASMString(qasm);
ASSERT_TRUE(qc);
EXPECT_EQ(qc->numGates(), 6);
EXPECT_EQ(qc->numSingleQubitGates(), 1);
EXPECT_EQ(qc->numTwoQubitGates(), 3);
}

/**
* @brief Test: gate counting includes each structured control-flow region once.
*/
TEST_F(CompilerPipelineTest, QCProgramCountGatesInStructuredControlFlow) {
const std::string qasm = R"(OPENQASM 3.0;
include "stdgates.inc";
qubit[3] q;
bit condition = measure q[0];
int selector = 1;
if (condition) {
for int i in [0:2] {
x q[i];
}
} else {
cx q[0], q[1];
}
while (condition) {
ctrl @ x q[0], q[1];
}
switch (selector) {
case 1 {
swap q[0], q[1];
}
default {
z q[2];
}
}
)";
auto qc = QCProgram::fromQASMString(qasm);
ASSERT_TRUE(qc);
EXPECT_EQ(qc->numGates(), 5);
EXPECT_EQ(qc->numSingleQubitGates(), 2);
EXPECT_EQ(qc->numTwoQubitGates(), 3);
}

} // namespace mqt::test::compiler
27 changes: 27 additions & 0 deletions python/mqt/core/mlir.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,33 @@ class QCProgram(Program):
Set ``copy=True`` to preserve it.
"""

def num_gates(self) -> int:
"""Count the gates in the program.

Any operation that implements the ``UnitaryOpInterface`` is counted. Operations
in every structured control-flow region are counted once, regardless of how
often the region executes. Operations within modifiers are not counted
recursively, and barriers are skipped.
"""

def num_single_qubit_gates(self) -> int:
"""Count the single-qubit gates in the program.

Any operation that implements the ``UnitaryOpInterface`` and acts on one qubit
is counted. Operations in every structured control-flow region are counted
once, regardless of how often the region executes. Operations within modifiers
are not counted recursively, and barriers are skipped.
"""

def num_two_qubit_gates(self) -> int:
"""Count the two-qubit gates in the program.

Any operation that implements the ``UnitaryOpInterface`` and acts on two qubits
is counted. Operations in every structured control-flow region are counted
once, regardless of how often the region executes. Operations within modifiers
are not counted recursively, and barriers are skipped.
"""

class QCOProgram(Program):
"""A compiler program in the QCO dialect.

Expand Down
8 changes: 8 additions & 0 deletions test/python/test_mlir.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,3 +674,11 @@ def test_compile_program_fails_for_missing_file() -> None:
"""A missing known input file extension raises an error."""
with pytest.raises(RuntimeError, match="does not exist"):
compile_program("missing_program.qasm")


def test_qc_program_num_gates() -> None:
"""Expose gate counts to Python."""
program = QCProgram.from_qasm_str(QASM_STRING)
assert program.num_gates() == 2
assert program.num_single_qubit_gates() == 1
assert program.num_two_qubit_gates() == 1
Loading