Skip to content
Merged
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 @@ -100,6 +100,9 @@ releases may include breaking changes.

### Fixed

- 馃悰 Initialize Qiskit classical bits before OpenQASM 3 serialization so
partially measured circuits preserve their zero values ([#2399])
([**@burgholzer**])
- 馃悰 Handle empty DDSIM results and NUL-terminated QDMI result buffers ([#2288])
([**@simon1hofmann**])
- 馃悰 Validate output permutations before I/O mapping initialization ([#2278])
Expand Down Expand Up @@ -816,6 +819,7 @@ for previous changelogs._

<!-- PR links -->

[#2399]: https://github.com/munich-quantum-toolkit/core/pull/2399
[#2368]: https://github.com/munich-quantum-toolkit/core/pull/2368
[#2358]: https://github.com/munich-quantum-toolkit/core/pull/2358
[#2349]: https://github.com/munich-quantum-toolkit/core/pull/2349
Expand Down
13 changes: 13 additions & 0 deletions python/mqt/core/plugins/qiskit/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,19 @@ def _serialize_to_qasm3(circuit: QuantumCircuit, backend: QDMIBackend) -> str:
Returns:
The OpenQASM 3 program.
"""
# Qiskit classical bits start at zero, while OpenQASM 3 bits are
# uninitialized. Preserve Qiskit's semantics and make every output valid
# even when the circuit measures only part of a register.
if circuit.num_clbits:
initialization = circuit.copy_empty_like(vars_mode="drop")
initialization.global_phase = 0
for clbit in initialization.clbits:
initialization.store(
clbit,
False, # ruff: ignore[boolean-positional-value-in-call] Qiskit store arguments are positional-only.
)
circuit = circuit.compose(initialization, front=True, inplace=False)

# Qiskit's OpenQASM3 exporter is fairly limited in terms of which gates it supports natively.
# So it needs some help from us.
exclusion_list = set()
Expand Down
10 changes: 10 additions & 0 deletions src/qasm3/Importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,16 @@ void Importer::visitAssignmentStatement(
return;
}

if (const auto constant = std::dynamic_pointer_cast<Constant>(
assignmentStatement->expression->expression);
assignmentStatement->type == AssignmentStatement::Assignment &&
constant && constant->isBool() && !constant->getBool() && qc->empty()) {
std::vector<qc::Bit> bits;
translateBitOperand(assignmentStatement->identifier, bits,
assignmentStatement->debugInfo);
return;
}

// In the future, handle classical computation.
throw CompilerError("Classical computation not supported.",
assignmentStatement->debugInfo);
Expand Down
14 changes: 12 additions & 2 deletions src/qasm3/passes/TypeCheckPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,20 @@ void TypeCheckPass::visitAssignmentStatement(
return;
}

if (!idTy->second.type->fits(*exprTy.type)) {
auto targetTy = idTy->second.type;
if (!assignmentStatement->identifier->indices.empty()) {
if (const auto designatedTy =
std::dynamic_pointer_cast<DesignatedType<uint64_t>>(targetTy)) {
targetTy =
std::make_shared<DesignatedType<uint64_t>>(designatedTy->type, 1);
}
}

if (!targetTy->fits(*exprTy.type) &&
!(targetTy->isConvertibleToBool() && exprTy.type->isBool())) {
std::stringstream ss;
ss << "Type mismatch in assignment. Expected '";
ss << idTy->second.type->toString();
ss << targetTy->toString();
ss << "', found '";
ss << exprTy.type->toString();
ss << "'.";
Expand Down
28 changes: 27 additions & 1 deletion test/ir/test_qasm3_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,32 @@ TEST_F(Qasm3ParserTest, ImportQasm3MeasureSingleQubit) {
EXPECT_EQ(qc.front()->getType(), qc::Measure);
}

TEST_F(Qasm3ParserTest, ImportQasm3ClassicalBitInitialization) {
const std::string testfile = "OPENQASM 3.0;\n"
"qubit[2] q;\n"
"bit[2] c;\n"
"c[0] = false;\n"
"c[1] = false;\n"
"c[0] = measure q[0];";
const auto qc = qasm3::Importer::imports(testfile);

EXPECT_EQ(qc.getNcbits(), 2);
ASSERT_EQ(qc.getNindividualOps(), 1);
EXPECT_EQ(qc.front()->getType(), qc::Measure);
}

TEST_F(Qasm3ParserTest, ImportQasm3RejectsClassicalBitReset) {
const std::string testfile = "OPENQASM 3.0;\n"
"qubit q;\n"
"bit c;\n"
"c = measure q;\n"
"c = false;";

EXPECT_THROW(
{ const auto qc = qasm3::Importer::imports(testfile); },
qasm3::CompilerError);
}

TEST_F(Qasm3ParserTest, ImportQasm3RejectsIndexingSingleQubit) {
const std::string testfile = "OPENQASM 3.0;\n"
"include \"stdgates.inc\";\n"
Expand Down Expand Up @@ -1783,7 +1809,7 @@ TEST_F(Qasm3ParserTest, ImportQasmAssignmentIndexType) {
const auto qc = qasm3::Importer::imports(testfile);
} catch (const qasm3::CompilerError& e) {
EXPECT_EQ(e.message, "Type Check Error: Type mismatch in assignment. "
"Expected 'bit[16]', found 'uint[32]'.");
"Expected 'bit[1]', found 'uint[32]'.");
throw;
}
},
Expand Down
19 changes: 19 additions & 0 deletions test/python/plugins/qiskit/test_mock_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,25 @@ def test_backend_qasm3_serialization_success(mock_qdmi_device_factory: type[Mock
assert "cx q[0], q[1]" in program


def test_backend_qasm3_zero_initializes_classical_bits(
mock_qdmi_device_factory: type[MockQDMIDevice],
) -> None:
"""Initialize every QASM 3 classical bit before measurement."""
qc = QuantumCircuit(2, 2)
qc.measure(0, 0)

device = mock_qdmi_device_factory(num_qubits=2, operations=["measure"])
backend = QDMIBackend(device) # ty: ignore[invalid-argument-type]

program, fmt = backend._serialize_circuit(qc, [ProgramFormat.QASM3]) # ruff:ignore[private-member-access]

assert fmt == ProgramFormat.QASM3
assert isinstance(program, str)
assert "c[0] = false;" in program
assert "c[1] = false;" in program
assert program.index("c[0] = false;") < program.index("c[0] = measure q[0];")


def test_backend_qasm2_serialization_success(mock_qdmi_device_factory: type[MockQDMIDevice]) -> None:
"""Backend should successfully serialize a circuit into OpenQASM 2."""
qc = QuantumCircuit(2)
Expand Down
Loading