| """ |
| qir_to_openqasm3.py |
| |
| Python implementation of QuantumIR → Heron-native OpenQASM 3.0 converter. |
| Includes ZNE stretching, mid-circuit measurement, DFE protocol, and |
| Richardson extrapolation. Runs in any sandbox (Kimi, Replit, local). |
| |
| Built from scratch — no Qiskit, no Cirq, no PennyLane dependency. |
| """ |
|
|
| import json |
| import random |
| import math |
|
|
|
|
| def decompose_to_heron(name, params, qubits, zne_factor): |
| instrs = [] |
| if name == "Rz": |
| instrs.append(f"rz({params[0]}) q[{qubits[0]}];") |
| elif name == "Rx": |
| q = qubits[0] |
| instrs.append(f"rz(-1.5707963267948966) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz({params[0]}) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz(1.5707963267948966) q[{q}];") |
| elif name == "Ry": |
| q = qubits[0] |
| instrs.append(f"rz(1.5707963267948966) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz({params[0]}) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz(-1.5707963267948966) q[{q}];") |
| elif name == "H": |
| q = qubits[0] |
| instrs.append(f"rz(1.5707963267948966) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz(1.5707963267948966) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz(1.5707963267948966) q[{q}];") |
| elif name == "S": |
| instrs.append(f"rz(1.5707963267948966) q[{qubits[0]}];") |
| elif name == "Sdg": |
| instrs.append(f"rz(-1.5707963267948966) q[{qubits[0]}];") |
| elif name == "T": |
| instrs.append(f"rz(0.7853981633974483) q[{qubits[0]}];") |
| elif name == "Tdg": |
| instrs.append(f"rz(-0.7853981633974483) q[{qubits[0]}];") |
| elif name == "X": |
| q = qubits[0] |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| elif name == "Y": |
| q = qubits[0] |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz(3.141592653589793) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| elif name == "Z": |
| instrs.append(f"rz(3.141592653589793) q[{qubits[0]}];") |
| elif name == "CX": |
| c, t = qubits[0], qubits[1] |
| instrs.append(f"cx q[{c}], q[{t}];") |
| if zne_factor > 1.0: |
| repeats = int(round(zne_factor)) - 1 |
| for _ in range(repeats): |
| instrs.append(f"cx q[{c}], q[{t}];") |
| instrs.append(f"cx q[{c}], q[{t}];") |
| elif name == "CZ": |
| c, t = qubits[0], qubits[1] |
| for h in decompose_to_heron("H", [], [t], 1.0): |
| instrs.append(h) |
| instrs.append(f"cx q[{c}], q[{t}];") |
| if zne_factor > 1.0: |
| repeats = int(round(zne_factor)) - 1 |
| for _ in range(repeats): |
| instrs.append(f"cx q[{c}], q[{t}];") |
| instrs.append(f"cx q[{c}], q[{t}];") |
| for h in decompose_to_heron("H", [], [t], 1.0): |
| instrs.append(h) |
| else: |
| instrs.append(f"// Unknown gate: {name}") |
| return instrs |
|
|
|
|
| def pauli_rotation_instrs(pauli, qubit): |
| if pauli == 'X': |
| return [ |
| f"rz(1.5707963267948966) q[{qubit}];", |
| f"sx q[{qubit}];", |
| f"rz(1.5707963267948966) q[{qubit}];", |
| f"sx q[{qubit}];", |
| f"rz(1.5707963267948966) q[{qubit}];" |
| ] |
| elif pauli == 'Y': |
| return [ |
| f"rz(-1.5707963267948966) q[{qubit}];", |
| f"sx q[{qubit}];", |
| f"rz(1.5707963267948966) q[{qubit}];", |
| f"sx q[{qubit}];", |
| f"rz(1.5707963267948966) q[{qubit}];" |
| ] |
| elif pauli in ('Z', 'I'): |
| return [] |
| return [f"// Unknown Pauli: {pauli}"] |
|
|
|
|
| def decompose_to_heron_zne(name, params, qubits): |
| instrs = [] |
| factor = "noise_factor" |
| if name == "Rz": |
| instrs.append(f"rz({params[0]} * {factor}) q[{qubits[0]}];") |
| elif name == "Rx": |
| q = qubits[0] |
| instrs.append(f"rz(-1.5707963267948966) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz({params[0]} * {factor}) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz(1.5707963267948966) q[{q}];") |
| elif name == "Ry": |
| q = qubits[0] |
| instrs.append(f"rz(1.5707963267948966) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz({params[0]} * {factor}) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz(-1.5707963267948966) q[{q}];") |
| elif name == "H": |
| q = qubits[0] |
| instrs.append(f"rz(1.5707963267948966) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz(1.5707963267948966) q[{q}];") |
| instrs.append(f"sx q[{q}];") |
| instrs.append(f"rz(1.5707963267948966) q[{q}];") |
| elif name == "CX": |
| c, t = qubits[0], qubits[1] |
| instrs.append(f"cx q[{c}], q[{t}];") |
| elif name == "CZ": |
| c, t = qubits[0], qubits[1] |
| for h in decompose_to_heron_zne("H", [], [t]): |
| instrs.append(h) |
| instrs.append(f"cx q[{c}], q[{t}];") |
| for h in decompose_to_heron_zne("H", [], [t]): |
| instrs.append(h) |
| else: |
| instrs.append(f"// {name} with ZNE not implemented") |
| return instrs |
|
|
|
|
| def qir_to_openqasm3(ir_dict, zne_factors=None, anu_bases=None, dynamic_shots=True): |
| if zne_factors is None: |
| zne_factors = [1.0] |
|
|
| nq = ir_dict["qubits"] |
| nc = ir_dict["cbits"] |
| ops = ir_dict["ops"] |
|
|
| if len(zne_factors) > 1 and dynamic_shots: |
| return qir_to_openqasm3_zne_dynamic(ir_dict, zne_factors, anu_bases) |
|
|
| factor = zne_factors[0] |
| lines = [] |
| indent = 0 |
|
|
| def emit(s=""): |
| lines.append(" " * indent + s) |
|
|
| emit("OPENQASM 3.0;") |
| emit('include "stdgates.inc";') |
| emit("") |
| emit(f"qubit[{nq}] q;") |
| emit(f"bit[{nc}] meas;") |
| emit("") |
| emit("float fidelity_sum = 0.0;") |
| emit("int valid_shots = 0;") |
| emit("") |
|
|
| if dynamic_shots and anu_bases is not None: |
| n_shots = len(anu_bases) |
| emit(f"for shot in [0:{n_shots-1}] {{") |
| indent += 2 |
|
|
| for op in ops: |
| op_type = op["type"] |
| if op_type == "gate": |
| for instr in decompose_to_heron(op["name"], op.get("params", []), op["qubits"], factor): |
| emit(instr) |
| elif op_type == "measure": |
| emit(f"meas[{op['cbit']}] = measure q[{op['qubit']}];") |
| elif op_type == "barrier": |
| qs = ", ".join(str(q) for q in op["qubits"]) |
| emit(f"barrier q[{qs}];") |
| elif op_type == "reset": |
| emit(f"if (meas[{op['qubit']}] == 1) {{ x q[{op['qubit']}]; }}") |
|
|
| if dynamic_shots and anu_bases is not None: |
| indent -= 2 |
| emit("}") |
|
|
| emit("") |
| emit("float kernel_est = fidelity_sum / float(valid_shots);") |
| emit("kernel_est;") |
|
|
| return "\n".join(lines) |
|
|
|
|
| def qir_to_openqasm3_zne_dynamic(ir_dict, zne_factors, anu_bases): |
| nq = ir_dict["qubits"] |
| nc = ir_dict["cbits"] |
| ops = ir_dict["ops"] |
| n_shots = len(anu_bases) if anu_bases else 1000 |
| n_factors = len(zne_factors) |
|
|
| lines = [] |
| indent = 0 |
|
|
| def emit(s=""): |
| lines.append(" " * indent + s) |
|
|
| emit("OPENQASM 3.0;") |
| emit('include "stdgates.inc";') |
| emit("") |
| emit(f"qubit[{nq}] q;") |
| emit(f"bit[{nc}] meas;") |
| emit("") |
| emit(f"float[{n_factors}] fidelity_sum = {{{', '.join(['0.0'] * n_factors)}}};") |
| emit(f"int[{n_factors}] valid_shots = {{{', '.join(['0'] * n_factors)}}};") |
| emit("") |
|
|
| if anu_bases is not None: |
| emit("// ANU QRNG Pauli bases (pre-fetched)") |
| emit(f"string[{n_shots * nq}] pauli_bases = {{") |
| indent += 2 |
| for shot, basis in enumerate(anu_bases[:n_shots]): |
| for q, pauli in enumerate(basis): |
| emit(f'"{pauli}", // shot {shot+1}, qubit {q}') |
| indent -= 2 |
| emit("};") |
| emit("") |
|
|
| emit(f"for f_idx in [0:{n_factors-1}] {{") |
| indent += 2 |
| emit(f"float noise_factors[{n_factors}] = {{{', '.join(str(f) for f in zne_factors)}}};") |
| emit("float noise_factor = noise_factors[f_idx];") |
| emit("") |
| emit(f"for shot in [0:{n_shots-1}] {{") |
| indent += 2 |
|
|
| if anu_bases is not None: |
| emit("// Pauli basis from ANU QRNG") |
| for q in range(nq): |
| emit(f'string pauli_{q} = pauli_bases[shot * {nq} + {q}];') |
|
|
| emit("// Feature Map U_Phi(x)") |
| for op in ops: |
| if op["type"] == "gate": |
| for instr in decompose_to_heron_zne(op["name"], op.get("params", []), op["qubits"]): |
| emit(instr) |
|
|
| emit("// Inverse Feature Map U_Phi(x')dagger") |
|
|
| if anu_bases is not None: |
| emit("// Pauli basis rotation") |
| for q in range(nq): |
| emit(f'if (pauli_{q} == "X") {{') |
| indent += 2 |
| for instr in pauli_rotation_instrs('X', q): |
| emit(instr) |
| indent -= 2 |
| emit(f'}} else if (pauli_{q} == "Y") {{') |
| indent += 2 |
| for instr in pauli_rotation_instrs('Y', q): |
| emit(instr) |
| indent -= 2 |
| emit("}") |
|
|
| emit("// Mid-circuit measurement") |
| for q in range(nq): |
| emit(f"meas[{q}] = measure q[{q}];") |
|
|
| emit("// Conditional reset") |
| for q in range(nq): |
| emit(f"if (meas[{q}] == 1) {{ x q[{q}]; }}") |
|
|
| emit("// DFE fidelity estimator") |
| emit("bool has_xy = false;") |
| emit("int z_weight = 0;") |
| if anu_bases is not None: |
| for q in range(nq): |
| emit(f'if (pauli_{q} == "X" || pauli_{q} == "Y") has_xy = true;') |
| emit(f'if (pauli_{q} == "Z") z_weight = z_weight + 1;') |
| emit("") |
| emit("if (!has_xy) {") |
| indent += 2 |
| emit("int eigenvalue = 1;") |
| if anu_bases is not None: |
| for q in range(nq): |
| emit(f'if (pauli_{q} == "Z" && meas[{q}] == 1) eigenvalue = eigenvalue * -1;') |
| emit("float estimator = pow(3.0, float(z_weight)) * float(eigenvalue);") |
| emit("fidelity_sum[f_idx] = fidelity_sum[f_idx] + estimator;") |
| emit("valid_shots[f_idx] = valid_shots[f_idx] + 1;") |
| indent -= 2 |
| emit("}") |
|
|
| indent -= 2 |
| emit("}") |
| indent -= 2 |
| emit("}") |
|
|
| emit("") |
| emit("// Richardson extrapolation to zero noise") |
| emit("float kernel_est = 0.0;") |
| for i in range(n_factors): |
| emit(f"float y{i} = fidelity_sum[{i}] / float(valid_shots[{i}]);") |
| for i in range(n_factors): |
| emit(f"float term{i} = y{i};") |
| for j in range(n_factors): |
| if i != j: |
| emit(f"term{i} = term{i} * (-{zne_factors[j]}) / ({zne_factors[i]} - {zne_factors[j]});") |
| emit(f"kernel_est = kernel_est + term{i};") |
| emit("") |
| emit("kernel_est;") |
|
|
| return "\n".join(lines) |
|
|
|
|
| if __name__ == "__main__": |
| import sys |
|
|
| if len(sys.argv) < 3: |
| print("Usage: python qir_to_openqasm3.py <input.ir.json> <output.qasm3> [zne_factors...]") |
| sys.exit(1) |
|
|
| input_file = sys.argv[1] |
| output_file = sys.argv[2] |
| zne_factors = [float(x) for x in sys.argv[3:]] if len(sys.argv) > 3 else [1.0] |
|
|
| with open(input_file, 'r') as f: |
| ir_list = json.load(f) |
|
|
| first_ir = ir_list[0] if isinstance(ir_list, list) else ir_list |
| n_qubits = first_ir["qubits"] |
| anu_bases = [[random.choice(['I', 'X', 'Y', 'Z']) for _ in range(n_qubits)] for _ in range(100)] |
|
|
| qasm = qir_to_openqasm3(first_ir, zne_factors=zne_factors, anu_bases=anu_bases, dynamic_shots=True) |
|
|
| with open(output_file, 'w') as f: |
| f.write(qasm) |
|
|
| print(f"Written {output_file} ({len(qasm)} chars, {len(qasm.splitlines())} lines)") |
| print(f"ZNE factors: {zne_factors}") |
| print(f"ANU QRNG shots: {len(anu_bases)}") |
|
|