From 5989cd5db25eb299e9eceef3e2adb57079e192d5 Mon Sep 17 00:00:00 2001 From: dsdsdshe Date: Fri, 14 Nov 2025 10:16:07 +0800 Subject: [PATCH] Fix(io): Correctly handle multiple measurements in OpenQASM export The OpenQASM export functionality previously created classical bits based on the set of unique qubits being measured. This led to incorrect behavior when a single qubit was measured multiple times within a circuit, as each measurement was mapped to the same classical bit. This patch corrects the issue by assigning a unique classical bit to every measurement operation, regardless of the target qubit. A new test case has been added to verify that circuits with repeated measurements on the same qubit are now correctly exported to OpenQASM. --- mindquantum/io/qasm/openqasm.py | 39 ++++++++++++++++++--- tests/st/test_io/test_qasm/test_openqasm.py | 24 +++++++++++++ 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/mindquantum/io/qasm/openqasm.py b/mindquantum/io/qasm/openqasm.py index 8285d820d..c593374ec 100644 --- a/mindquantum/io/qasm/openqasm.py +++ b/mindquantum/io/qasm/openqasm.py @@ -17,6 +17,8 @@ import ast import operator as op import re +from dataclasses import dataclass +from typing import List import numpy as np @@ -396,6 +398,29 @@ def generate_custom_gate(gate_def, custom_gate, gate_map_openqasm_mq): custom_gate[gate_name] = gate +@dataclass(frozen=True) +class MeasurementSlot: + """Book-keeping entry describing how a measurement maps to a classical bit.""" + + gate_id: int + qubit: int + key: str + bit: int + + +def collect_measurement_layout(circ) -> List[MeasurementSlot]: + """Return an ordered mapping from measurement gates to classical bits.""" + from mindquantum.core import gates as G # noqa: N812 + + slots: List[MeasurementSlot] = [] + for gate in circ: + if isinstance(gate, G.Measure): + qubit = gate.obj_qubits[0] + key = gate.key or f"q{qubit}" + slots.append(MeasurementSlot(id(gate), qubit, key, len(slots))) + return slots + + def mq_to_qasm_v2(circ, gate_map_mq_openqasm, version: str = '2.0'): """Convert mindquantum circuit to openqasm.""" from mindquantum.algorithm.compiler import BasicDecompose, compile_circuit @@ -407,10 +432,10 @@ def mq_to_qasm_v2(circ, gate_map_mq_openqasm, version: str = '2.0'): if circ.is_noise_circuit: raise ValueError("Cannot convert noise circuit to qasm.") cmds = ['OPENQASM 2.0;', 'include "qelib1.inc";', f'qreg q[{circ.n_qubits}];'] - mea_qubits = sorted({m.obj_qubits[0] for m in circ.all_measures.keys()}) - reg_map = {j: i for i, j in enumerate(mea_qubits)} - if mea_qubits: - cmds.append(f"creg c[{len(mea_qubits)}];") + measurement_slots = collect_measurement_layout(circ) + if measurement_slots: + cmds.append(f"creg c[{len(measurement_slots)}];") + gate_to_slot = {slot.gate_id: slot for slot in measurement_slots} for gate in circ: gate: G.BasicGate if isinstance(gate, G.BarrierGate): @@ -420,7 +445,11 @@ def mq_to_qasm_v2(circ, gate_map_mq_openqasm, version: str = '2.0'): cmds.append(f"barrier {join_qubit(G.BARRIER.on(obj))};") continue if isinstance(gate, G.Measure): - cmds.append(f"measure q[{gate.obj_qubits[0]}] -> c[{reg_map[gate.obj_qubits[0]]}];") + slot = gate_to_slot.get(id(gate)) + if slot is None: + raise ValueError("Measurement gate missing from layout map during OpenQASM export.") + key_comment = f" // key:{slot.key}" if slot.key else '' + cmds.append(f"measure q[{slot.qubit}] -> c[{slot.bit}];{key_comment}") continue if gate.__class__ in gate_map_mq_openqasm: cmds.append(gate_map_mq_openqasm[gate.__class__](gate)) diff --git a/tests/st/test_io/test_qasm/test_openqasm.py b/tests/st/test_io/test_qasm/test_openqasm.py index 119006299..ab15dbc16 100644 --- a/tests/st/test_io/test_qasm/test_openqasm.py +++ b/tests/st/test_io/test_qasm/test_openqasm.py @@ -17,6 +17,7 @@ import numpy as np from mindquantum.core import U3, UN, Circuit, H, X +from mindquantum.core.gates import Measure from mindquantum.io import OpenQASM from mindquantum.simulator import Simulator from mindquantum.utils import random_circuit @@ -179,3 +180,26 @@ measure q -> c; s_1 = sim.sampling(init + circ, shots=50, seed=42) s_2 = sim.sampling(init + exp_circ, shots=50, seed=42) assert np.all(s_1._samples == s_2._samples) + + +def test_openqasm_distinguishes_repeated_measurements(): + """ + Description: Ensure multiple measurements on the same qubit map to unique classical bits. + Expectation: success + """ + + circ = Circuit().h(0) + circ += Measure('m0').on(0) + circ += H.on(0) + circ += Measure('m1').on(0) + + qasm = OpenQASM().to_string(circ) + lines = qasm.splitlines() + assert 'creg c[2];' in lines + + measure_lines = [line for line in lines if line.startswith('measure')] + assert len(measure_lines) == 2 + assert measure_lines[0].startswith('measure q[0] -> c[0];') + assert 'key:m0' in measure_lines[0] + assert measure_lines[1].startswith('measure q[0] -> c[1];') + assert 'key:m1' in measure_lines[1] -- Gitee