Skip to content

IR Conversion -- OriginIR and QASM ​

Convert between QProg, OriginIR, and QASM intermediate representations for circuit interoperability, serialization, and debugging.

For the OriginIR grammar itself (program structure, gate declarations, angle expressions, DAGGER/CONTROL, QIF/QWHILE, custom gates, and a common-error reference), see the OriginIR Syntax chapter.


Problem ​

Quantum circuits built with pyqpanda3 exist as in-memory QProg objects. In many situations you need to represent a circuit as a plain-text string so that it can be stored, transmitted, or shared with other frameworks. Two text-based intermediate representations (IRs) are supported:

OriginIR is QPanda's native IR format. It provides full fidelity for every pyqpanda3 feature, including dynamic circuits (QIF/QELSE/QWHILE), custom gate definitions (QGATE), controlled and daggered operations, and all native gate types. Use OriginIR when you need to serialize and deserialize circuits within the pyqpanda3 ecosystem.

OpenQASM 2.0 is the industry-standard quantum assembly language supported by Qiskit, Cirq, and many other frameworks. Use QASM when you need to exchange circuits with non-QPanda tools, import circuits from the open-source community, or export circuits for publication.

You need IR conversion when:

  • Interoperability -- Import a circuit authored in Qiskit via QASM, or export a pyqpanda3 circuit for use in another framework.
  • Persistence -- Save a circuit to disk as an OriginIR or QASM file, then load it later without re-running the construction code.
  • Verification -- Export a circuit to text, visually inspect the gate sequence, or compare two circuits by comparing their string representations.
  • Debugging -- Print the IR of a complex QProg to understand its gate structure, control flow, and measurement operations.
  • Round-trip testing -- Build a circuit, export it to IR, import it back, and verify the unitary matrices match to confirm correctness of the conversion pipeline.

The following diagram shows the conversion paths supported by the intermediate_compiler module:

mermaid
flowchart LR
    QProg["QProg<br/>(in-memory)"]

    subgraph Export
        E1["convert_qprog_to_originir()"]
        E2["convert_qprog_to_qasm()"]
    end

    subgraph Import
        I1["convert_originir_string_to_qprog()"]
        I2["convert_originir_file_to_qprog()"]
        I3["convert_qasm_string_to_qprog()"]
        I4["convert_qasm_file_to_qprog()"]
    end

    OIR["OriginIR<br/>string / file"]
    QASM["QASM 2.0<br/>string / file"]

    QProg --> E1 --> OIR
    QProg --> E2 --> QASM
    OIR --> I1 --> QProg
    OIR --> I2 --> QProg
    QASM --> I3 --> QProg
    QASM --> I4 --> QProg

    style QProg fill:#4a90d9,color:#fff,stroke:#2c5f9e
    style OIR fill:#50c878,color:#fff,stroke:#2d8a4e
    style QASM fill:#f4a460,color:#fff,stroke:#b87333

Solution ​

The intermediate_compiler module provides six functions that cover all conversion directions:

FunctionDirectionInputOutput
convert_qprog_to_originir(prog, precision)QProg -> OriginIRQProg, optional intstr
convert_qprog_to_qasm(prog, precision)QProg -> QASMQProg, optional intstr
convert_originir_string_to_qprog(ir_string)OriginIR str -> QProgstrQProg
convert_originir_file_to_qprog(file_path)OriginIR file -> QProgstr (path)QProg
convert_qasm_string_to_qprog(qasm_string)QASM str -> QProgstrQProg
convert_qasm_file_to_qprog(file_path)QASM file -> QProgstr (path)QProg

The precision parameter (default: 8) controls the number of decimal places used for floating-point angle values in the exported string. A higher precision preserves more accuracy, which is important for round-trip fidelity.

One boundary applies to the export direction. The native RESET gate introduced in 0.5.0 is not representable in the QASM 2.0 subset that convert_qprog_to_qasm() emits, so exporting a program that contains RESET raises a RuntimeError:

python
from pyqpanda3.core import QProg, RESET, X
from pyqpanda3.intermediate_compiler import convert_qprog_to_qasm, convert_qprog_to_originir

prog = QProg() << X(0) << RESET(0)
print("OriginIR:", convert_qprog_to_originir(prog).strip().splitlines()[-1])

try:
    convert_qprog_to_qasm(prog)
except RuntimeError as e:
    print("QASM:", type(e).__name__, "-", str(e).splitlines()[0])

Output:

text
OriginIR: RESET q[0]
QASM: RuntimeError - QGate::matrix() error gate identifier.

When a program contains RESET, serialize it through OriginIR. The import direction is unaffected: convert_qasm_string_to_qprog() parses an OpenQASM reset q[i]; statement into the native gate, so a QASM program that uses reset can be imported and then re-exported as OriginIR.

Step 1: Export a circuit to OriginIR or QASM ​

Build a QProg using the standard pyqpanda3 API, then call the appropriate conversion function:

python
from pyqpanda3.core import QProg, H, CNOT, RX, measure
from pyqpanda3.intermediate_compiler import (
    convert_qprog_to_originir,
    convert_qprog_to_qasm,
)

prog = QProg()
prog << H(0)
prog << CNOT(0, 1)
prog << RX(1, 3.14159)
prog << measure([0, 1], [0, 1])

originir_str = convert_qprog_to_originir(prog)
qasm_str = convert_qprog_to_qasm(prog)

print("=== OriginIR ===")
print(originir_str)
print("=== QASM ===")
print(qasm_str)

运行结果:

=== OriginIR ===
QINIT 2
CREG 2
H q[0]
CNOT q[0],q[1]
RX q[1],(3.14159000)
MEASURE q[0],c[0]
MEASURE q[1],c[1]

=== QASM ===
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
cx q[0],q[1];
rx(3.14159000) q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];

Step 2: Import an OriginIR or QASM string into a QProg ​

Parse a text representation back into an executable QProg:

python
from pyqpanda3.intermediate_compiler import (
    convert_originir_string_to_qprog,
    convert_qasm_string_to_qprog,
)

originir_str = """
    QINIT 2
    CREG 2
    H q[0]
    CNOT q[0],q[1]
    MEASURE q[0],c[0]
    MEASURE q[1],c[1]
"""

prog_from_originir = convert_originir_string_to_qprog(originir_str)

Step 3: Load a circuit from a file ​

For circuits stored on disk, use the file-based import functions:

python
from pyqpanda3.intermediate_compiler import (
    convert_originir_file_to_qprog,
    convert_qasm_file_to_qprog,
)

import tempfile
import os

# Create a temporary OriginIR file
with tempfile.NamedTemporaryFile(mode='w', suffix='.originir', delete=False) as f:
    f.write('QINIT 2\nCREG 2\nH q[0]\nCNOT q[0],q[1]\nMEASURE q[0],c[0]\nMEASURE q[1],c[1]')
    tmp_path = f.name

# Load from OriginIR file
prog = convert_originir_file_to_qprog(tmp_path)

# Also loads QASM files (replace with your actual file path)
with open(tmp_path + '.qasm', 'w') as fq:
    fq.write('OPENQASM 2.0;\ninclude "qelib1.inc";\nqreg q[2];\ncreg c[2];\nh q[0];\ncx q[0],q[1];\n')
prog = convert_qasm_file_to_qprog(tmp_path + '.qasm')
print("Circuits loaded successfully")

Step 4: Verify round-trip fidelity ​

After a round-trip conversion (export then import), compare the unitary matrices to confirm no information was lost:

python
from pyqpanda3.quantum_info import Unitary

prog = QProg()
prog << H(0) << CNOT(0, 1)

qasm_str = convert_qprog_to_qasm(prog, precision=15)
prog2 = convert_qasm_string_to_qprog(qasm_str)

mat1 = Unitary(prog.to_circuit())
mat2 = Unitary(prog2.to_circuit())

# Compute squared distance between the two unitary matrices
diff = abs(mat1.ndarray() - mat2.ndarray()) ** 2
print(f"Round-trip error: {diff.max():.2e}")

运行结果:

Round-trip error: 0.00e+00

Code ​

Example 1: Build a Bell State and Export to Both Formats ​

This example creates a Bell state circuit and exports it to both OriginIR and QASM:

python
from pyqpanda3.core import QProg, H, CNOT, measure
from pyqpanda3.intermediate_compiler import (
    convert_qprog_to_originir,
    convert_qprog_to_qasm,
)

# Build a Bell state circuit
prog = QProg()
prog << H(0)
prog << CNOT(0, 1)
prog << measure([0, 1], [0, 1])

# Export to OriginIR (QPanda's native format)
originir_str = convert_qprog_to_originir(prog)
print("OriginIR representation:")
print(originir_str)
print()

# Export to QASM 2.0 (industry standard)
qasm_str = convert_qprog_to_qasm(prog)
print("QASM representation:")
print(qasm_str)

运行结果:

OriginIR representation:
QINIT 2
CREG 2
H q[0]
CNOT q[0],q[1]
MEASURE q[0],c[0]
MEASURE q[1],c[1]


QASM representation:
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
cx q[0],q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];

Expected output (both OriginIR and QASM representations are printed):

OriginIR representation:
QINIT 2
CREG 2
H q[0]
CNOT q[0],q[1]
MEASURE q[0],c[0]
MEASURE q[1],c[1]


QASM representation:
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
cx q[0],q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];

Expected QASM output:

OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
cx q[0],q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];

Example 2: Import an OriginIR String and Run It ​

This example parses an OriginIR string, runs the resulting circuit on a simulator, and prints the measurement counts:

python
from pyqpanda3.core import CPUQVM
from pyqpanda3.intermediate_compiler import convert_originir_string_to_qprog

originir_str = """
    QINIT 3
    CREG 2
    H q[2]
    H q[0]
    H q[1]
    CONTROL q[1]
    RX q[2],(-3.141593)
    ENDCONTROL
    CONTROL q[0]
    RX q[2],(-3.141593)
    RX q[2],(-3.141593)
    ENDCONTROL
    DAGGER
    H q[1]
    H q[0]
    ENDDAGGER
    MEASURE q[0],c[0]
    MEASURE q[1],c[1]
"""

prog = convert_originir_string_to_qprog(originir_str)

machine = CPUQVM()
machine.run(prog=prog, shots=1000)

counts = machine.result().get_counts()
print("Measurement counts:", counts)

运行结果:

Measurement counts: {'11': 494, '01': 506}

Example 3: Import a QASM String and Run It ​

This example imports a quantum teleportation circuit written in QASM:

python
from pyqpanda3.core import CPUQVM
from pyqpanda3.intermediate_compiler import convert_qasm_string_to_qprog

qasm_str = """
    // quantum teleportation example
    OPENQASM 2.0;
    include "qelib1.inc";
    qreg q[3];
    creg c[3];
    gate post q {
        h q;
    }
    u3(0.3,0.2,0.1) q[0];
    h q[1];
    cx q[1],q[2];
    barrier q;
    cx q[0],q[1];
    h q[0];
    measure q[0] -> c[0];
    measure q[1] -> c[1];
    if(c==1) z q[2];
    if(c==2) x q[2];
    if(c==3) y q[2];
    post q[2];
    measure q[2] -> c[2];
"""

prog = convert_qasm_string_to_qprog(qasm_str)

machine = CPUQVM()
machine.run(prog=prog, shots=1000)

counts = machine.result().get_counts()
print("Teleportation counts:", counts)

运行结果:

Teleportation counts: {'100': 77, '110': 87, '101': 96, '001': 158, '011': 96, '111': 154, '000': 173, '010': 159}

Example 4: Round-Trip Conversion with Verification ​

This example demonstrates a full round-trip: build a circuit, export to QASM, import back, and verify that the unitary matrices match. This is a common pattern for validating that the conversion pipeline preserves circuit semantics.

python
from pyqpanda3.core import QProg, H, CNOT, RX, RY, RZ, CZ
from pyqpanda3.intermediate_compiler import (
    convert_qprog_to_qasm,
    convert_qasm_string_to_qprog,
    convert_qprog_to_originir,
)
from pyqpanda3.quantum_info import Unitary
import numpy as np

# Build a non-trivial circuit
prog = QProg()
prog << H(0)
prog << H(1)
prog << CNOT(0, 1)
prog << RZ(0, 1.2345)
prog << RZ(1, 2.3456)
prog << CNOT(0, 1)
prog << RX(0, 0.5678)
prog << RY(1, 0.9012)
prog << CZ(0, 1)

# Use high precision for the round-trip to avoid floating-point loss
qasm_str = convert_qprog_to_qasm(prog, precision=15)
print("Exported QASM:")
print(qasm_str)

# Import the QASM string back into a QProg
prog2 = convert_qasm_string_to_qprog(qasm_str)

# Also verify the OriginIR path
originir_str = convert_qprog_to_originir(prog, precision=15)
print("\nExported OriginIR:")
print(originir_str)

# Compare unitary matrices
mat1 = Unitary(prog.to_circuit())
mat2 = Unitary(prog2.to_circuit())

diff = np.abs(mat1.ndarray() - mat2.ndarray()) ** 2
max_error = diff.max()
print(f"\nRound-trip max squared error: {max_error:.2e}")
assert max_error < 1e-10, "Round-trip fidelity check failed!"
print("Round-trip verified successfully.")

运行结果:

Exported QASM:
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[0];
h q[0];
h q[1];
cx q[0],q[1];
rz(1.234500000000000) q[0];
rz(2.345600000000000) q[1];
cx q[0],q[1];
rx(0.567800000000000) q[0];
ry(0.901200000000000) q[1];
cz q[0],q[1];

Exported OriginIR:
QINIT 2
CREG 1
H q[0]
H q[1]
CNOT q[0],q[1]
RZ q[0],(1.234500000000000)
RZ q[1],(2.345600000000000)
CNOT q[0],q[1]
RX q[0],(0.567800000000000)
RY q[1],(0.901200000000000)
CZ q[0],q[1]


Round-trip max squared error: 0.00e+00
Round-trip verified successfully.

Example 5: Saving Circuits to Files and Loading Them ​

This example shows how to write an OriginIR or QASM string to a file and read it back:

python
import os
from pyqpanda3.core import QProg, H, CNOT, measure
from pyqpanda3.intermediate_compiler import (
    convert_qprog_to_originir,
    convert_qprog_to_qasm,
    convert_originir_file_to_qprog,
    convert_qasm_file_to_qprog,
)

# Build a circuit
prog = QProg()
prog << H(0)
prog << CNOT(0, 1)
prog << measure([0, 1], [0, 1])

# --- Save as OriginIR ---
originir_str = convert_qprog_to_originir(prog)
originir_path = "/tmp/bell_circuit.txt"
with open(originir_path, "w") as f:
    f.write(originir_str)
print(f"Saved OriginIR to {originir_path}")

# --- Save as QASM ---
qasm_str = convert_qprog_to_qasm(prog)
qasm_path = "/tmp/bell_circuit.qasm"
with open(qasm_path, "w") as f:
    f.write(qasm_str)
print(f"Saved QASM to {qasm_path}")

# --- Load back from files ---
prog_from_oir = convert_originir_file_to_qprog(originir_path)
prog_from_qasm = convert_qasm_file_to_qprog(qasm_path)

# Verify both loaded circuits produce the same OriginIR
print("\nOriginIR from file:")
print(convert_qprog_to_originir(prog_from_oir))
print("\nQASM from file:")
print(convert_qprog_to_qasm(prog_from_qasm))

# Clean up
os.remove(originir_path)
os.remove(qasm_path)

运行结果:

Saved OriginIR to /tmp/bell_circuit.txt
Saved QASM to /tmp/bell_circuit.qasm

OriginIR from file:
QINIT 2
CREG 2
H q[0]
CNOT q[0],q[1]
MEASURE q[0],c[0]
MEASURE q[1],c[1]


QASM from file:
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
cx q[0],q[1];
measure q[0] -> c[0];
measure q[1] -> c[1];

Example 6: Working with Controlled and Daggered Operations ​

OriginIR natively supports CONTROL/ENDCONTROL blocks and DAGGER/ENDDAGGER blocks. This example demonstrates how these map between the QProg API and the IR:

python
from pyqpanda3.core import QProg, H, RX, X
from pyqpanda3.intermediate_compiler import convert_qprog_to_originir

prog = QProg()
prog << H(0)
prog << H(1)
prog << H(2)

# Apply RX on qubit 2, controlled by qubit 1
prog << RX(2, -3.141593).control([1])

# Apply RX on qubit 2, controlled by qubit 0 (twice, effectively 2x the angle)
prog << RX(2, -3.141593).control([0])
prog << RX(2, -3.141593).control([0])

# Apply the dagger (inverse) of H(1) followed by H(0)
prog << H(1).dagger()
prog << H(0).dagger()

originir_str = convert_qprog_to_originir(prog)
print(originir_str)

运行结果:

QINIT 3
CREG 1
H q[0]
H q[1]
H q[2]
CONTROL q[1]
RX q[2],(-3.14159300)
ENDCONTROL
CONTROL q[0]
RX q[2],(-3.14159300)
ENDCONTROL
CONTROL q[0]
RX q[2],(-3.14159300)
ENDCONTROL
DAGGER
H q[1]
ENDDAGGER
DAGGER
H q[0]
ENDDAGGER

The output shows CONTROL/ENDCONTROL and DAGGER/ENDDAGGER blocks. Each .control() call creates its own CONTROL block, and each .dagger() call creates its own DAGGER block.

Example 7: Custom Gate Definitions in OriginIR ​

OriginIR supports user-defined gates via the QGATE/ENDQGATE syntax. You can define reusable gate blocks with parameters:

python
from pyqpanda3.core import CPUQVM
from pyqpanda3.intermediate_compiler import convert_originir_string_to_qprog

originir_str = """
    QINIT 2
    CREG 2

    H q
    X q[0]
    Y q[1]

    QGATE new_H a,b
    Y a
    Z b
    ENDQGATE

    QGATE new_RX a,b,(d,e)
    H a
    RX b,(PI/2+d+e)
    new_H a,b
    ENDQGATE

    new_RX q[1],q[0],(PI/2,3)
    new_RX q,(1,2)

    MEASURE q[0],c[0]
    MEASURE q[1],c[1]
"""

prog = convert_originir_string_to_qprog(originir_str)

machine = CPUQVM()
machine.run(prog=prog, shots=1000)
counts = machine.result().get_counts()
print("Counts:", counts)

运行结果:

Custom Gate: new_H
Qbit Params: a=1
Qbit Params: b=0

Gate Operations: 
	Y 1,
Gate Operations: 
	Z 0,
Custom Gate: new_H
Qbit Params: a=0
Qbit Params: b=1

Gate Operations: 
	Y 0,
Gate Operations: 
	Z 1,
Counts: {'10': 574, '00': 426}

The QGATE block above defines new_H as a composite of Y and Z on two qubits, and new_RX as a parameterized gate that uses PI as a built-in constant and references parameters d and e.

Example 8: Dynamic Circuits (QIF / QWHILE) in OriginIR ​

OriginIR supports dynamic circuit constructs -- conditional execution (QIF/QELSE/QELSEIF/QENDIF) and mid-circuit loops (QWHILE/QENDWHILE). These are essential for circuits where subsequent operations depend on earlier measurement results:

python
from pyqpanda3.core import QProg, CPUQVM, X, Y, Z, H, measure, qif, qwhile
from pyqpanda3.intermediate_compiler import (
    convert_qprog_to_originir,
    convert_originir_string_to_qprog,
)

# Build a dynamic circuit using the pyqpanda3 API
prog = QProg()
inner_loop = QProg()
inner_loop << Y(2) << Y(1) << measure(2, 2)

if_body = QProg()
if_body << Y(0) << H(1)
if_body << qwhile([2]).loop(inner_loop)

prog << X(1)
prog << qif([1]).then(if_body).qendif()
prog << measure(0, 0)
prog << measure(1, 1)

# Export to OriginIR to see the dynamic circuit representation
originir_str = convert_qprog_to_originir(prog)
print("Dynamic circuit as OriginIR:")
print(originir_str)

# Parse it back and verify the round-trip
prog2 = convert_originir_string_to_qprog(originir_str)
originir_str2 = convert_qprog_to_originir(prog2)
print("\nRound-trip OriginIR:")
print(originir_str2)

# Run on a simulator
machine = CPUQVM()
machine.run(prog=prog, shots=100)
state = machine.result().get_state_vector()
print("\nState vector (first 4 amplitudes):")
for i, v in enumerate(state[:4]):
    print(f"  |{i:02b}>: ({v.real:.6f}, {v.imag:.6f})")

运行结果:

Dynamic circuit as OriginIR:
QINIT 3
CREG 3
X q[1]
QIF c[1]
Y q[0]
H q[1]
QWHILE c[2]
Y q[2]
Y q[1]
MEASURE q[2],c[2]
QENDWHILE
QENDIF
MEASURE q[0],c[0]
MEASURE q[1],c[1]


Round-trip OriginIR:
QINIT 3
CREG 3
X q[1]
QIF c[1]
Y q[0]
H q[1]
QWHILE c[2]
Y q[2]
Y q[1]
MEASURE q[2],c[2]
QENDWHILE
QENDIF
MEASURE q[0],c[0]
MEASURE q[1],c[1]


State vector (first 4 amplitudes):
  |00>: (0.000000, 0.000000)
  |01>: (0.000000, 0.000000)
  |10>: (0.000000, 0.000000)
  |11>: (0.000000, -1.000000)

Note: The state vector output is non-deterministic because the dynamic circuit's execution path depends on mid-circuit measurement results (QIF condition on c[1] and QWHILE condition on c[2]). Actual output will vary between runs.


Explanation ​

OriginIR Syntax Overview ​

OriginIR is a line-oriented, text-based IR. Each line contains a single instruction or a block delimiter. The general structure is:

QINIT <n>           -- Declare n qubits
CREG <m>            -- Declare m classical bits

<gate> q[<idx>]     -- Single-qubit gate
<gate> q[<i>],q[<j>]  -- Two-qubit gate
<gate> q[<i>],(<angle>)  -- Parameterized gate with angle in radians

Header lines (QINIT and CREG) are required. They declare the number of qubits and classical bits. The qubit index range is [0,n−1] where n is the QINIT value.

Gate instructions follow the pattern GATE_NAME target_qubit(s),(parameter(s)). For example:

OriginIR LineMeaning
H q[0]Hadamard on qubit 0
X q[1]Pauli-X on qubit 1
RX q[0],(1.570796)Rotation around X axis by π/2 radians
CNOT q[0],q[1]CNOT with control=0, target=1
MEASURE q[0],c[0]Measure qubit 0 into classical bit 0

Control blocks use CONTROL/ENDCONTROL:

CONTROL q[0],q[1]
X q[2]
Z q[3]
ENDCONTROL

This applies the enclosed gates only when both qubit 0 and qubit 1 are in the |1⟩ state.

Dagger blocks use DAGGER/ENDDAGGER to apply the inverse of the enclosed operations:

DAGGER
H q[0]
RX q[1],(1.570796)
ENDDAGGER

Custom gates use QGATE/ENDQGATE with optional parameters:

QGATE my_gate a,b,(theta,phi)
  RX a,(theta)
  RY b,(phi)
  CNOT a,b
ENDQGATE

Here a and b are formal qubit arguments, and theta and phi are formal parameter names. The built-in constant PI represents π≈3.14159265358979.

Conditional execution uses QIF/QELSE/QELSEIF/QENDIF:

QIF c[0],c[1]
  X q[2]
QELSEIF c[2]
  Y q[2]
QELSE
  Z q[2]
QENDIF

The condition checks whether the specified classical bits, when interpreted as a little-endian integer, equal 1. Multiple classical bits can be listed.

Loop constructs use QWHILE/QENDWHILE:

QWHILE c[0]
  X q[1]
  MEASURE q[1],c[0]
QENDWHILE

The loop body executes repeatedly as long as the condition classical bit evaluates to 1.

Comments are single-line comments beginning with //:

H q[0]         // Apply Hadamard
// This is a full-line comment

QASM 2.0 Syntax Overview ​

QASM 2.0 is the standard quantum assembly format defined by IBM. A QASM 2.0 program has this structure:

text
OPENQASM 2.0;
include "qelib1.inc";
qreg q[<n>];
creg c[<m>];
<gate> q[<i>];
<gate> q[<i>],q[<j>];
measure q[<i>] -> c[<j>];

Key differences from OriginIR:

  • The header is OPENQASM 2.0; followed by include "qelib1.inc";.
  • Qubit registers use qreg q[n]; instead of QINIT n.
  • Classical registers use creg c[m]; instead of CREG m.
  • The measurement syntax uses -> instead of a comma: measure q[0] -> c[0].
  • Gate names are lowercase: h, cx, cz, rx, ry, rz, etc.
  • QASM supports custom gate definitions via the gate keyword.
  • QASM supports conditional execution via if(c==<val>) syntax.

Gate Mapping Between Representations ​

The following table shows how common gates map between pyqpanda3, OriginIR, and QASM 2.0:

pyqpanda3 APIOriginIRQASM 2.0
H(q)H q[i]h q[i]
X(q)X q[i]x q[i]
Y(q)Y q[i]y q[i]
Z(q)Z q[i]z q[i]
S(q)S q[i]s q[i]
T(q)T q[i]t q[i]
RX(q, angle)RX q[i],(angle)rx(angle) q[i]
RY(q, angle)RY q[i],(angle)ry(angle) q[i]
RZ(q, angle)RZ q[i],(angle)rz(angle) q[i]
P(q, angle)P q[i],(angle)p(angle) q[i]
CNOT(c, t)CNOT q[c],q[t]cx q[c],q[t]
CZ(c, t)CZ q[c],q[t]cz q[c],q[t]
SWAP(a, b)SWAP q[a],q[b]swap q[a],q[b]
TOFFOLI(a, b, t)TOFFOLI q[a],q[b],q[t]ccx q[a],q[b],q[t]
U1(q, lam)U1 q[i],(lam)u1(lam) q[i]
U2(q, phi, lam)U2 q[i],(phi,lam)u2(phi,lam) q[i]
U3(q, theta, phi, lam)U3 q[i],(theta,phi,lam)u3(theta,phi,lam) q[i]
CU(c, t, theta, phi, lam, gamma)CU q[c],q[t],(theta,phi,lam,gamma)cu(theta,phi,lam,gamma) q[c],q[t]
measure(q, c)MEASURE q[i],c[j]measure q[i] -> c[j]
BARRIER(qubits)BARRIER q[i],q[j],...barrier q[i],q[j],...

Limitations and Format Selection ​

Each format has specific strengths and limitations:

mermaid
flowchart TD
    subgraph OriginIR
        O1["Full gate set"]
        O2["Dynamic circuits QIF/QWHILE"]
        O3["Custom gate QGATE"]
        O4["CONTROL / DAGGER blocks"]
        O5["QProg constructor"]
    end

    subgraph QASM20["QASM 2.0"]
        Q1["Standard gate set"]
        Q2["gate keyword definitions"]
        Q3["Conditional if(c==val)"]
        Q4["Barrier support"]
        Q5["Industry standard"]
    end

    subgraph QASMLim["QASM Limitations"]
        QL1["No QWHILE loops"]
        QL2["No RPHI gate"]
        QL3["No ISWAP gate"]
        QL4["Limited dynamic circuits"]
    end

    subgraph OIRLim["OriginIR Limitations"]
        OL1["QPanda-specific"]
        OL2["Limited external support"]
    end

    style OriginIR fill:#50c878,color:#fff,stroke:#2d8a4e
    style QASM20 fill:#f4a460,color:#fff,stroke:#b87333
    style QASMLim fill:#e74c3c,color:#fff,stroke:#c0392b
    style OIRLim fill:#e74c3c,color:#fff,stroke:#c0392b

When to use OriginIR:

  • Serializing pyqpanda3 circuits for storage and later retrieval.
  • Circuits with dynamic control flow (QIF, QWHILE).
  • Circuits using RPHI, ISWAP, SQISWAP, or other QPanda-native gates.
  • Full-fidelity round-trips within the pyqpanda3 ecosystem.
  • Quick circuit construction using QProg(ir_string).

When to use QASM 2.0:

  • Exchanging circuits with Qiskit, Cirq, or other frameworks.
  • Importing circuits from public repositories or textbooks.
  • Publishing circuits in a widely understood format.
  • Submitting to quantum cloud services that accept QASM.

Key limitations to be aware of:

  • QASM 2.0 does not support QWHILE loops. If you export a circuit containing a qwhile construct to QASM, the loop structure will not be preserved.
  • Some QPanda-native gates (e.g., ISWAP, RPHI) do not have direct QASM 2.0 equivalents. Exporting these gates to QASM may produce an error or decompose them into supported gates.
  • The precision parameter affects floating-point fidelity. For circuits with many rotation gates, use precision=15 or higher to ensure round-trip accuracy.
  • OriginIR and QASM use different conventions for the measure syntax: OriginIR uses MEASURE q[i],c[j] while QASM uses measure q[i] -> c[j].

Convenience: QProg OriginIR Constructor ​

In addition to the convert_originir_string_to_qprog function, you can also construct a QProg directly from an OriginIR string using the QProg constructor:

python
from pyqpanda3.core import QProg, CPUQVM

# Direct construction from OriginIR string
prog = QProg("""
    QINIT 2
    CREG 2
    H q[0]
    CNOT q[0],q[1]
    MEASURE q[0],c[0]
    MEASURE q[1],c[1]
""")

# The program is ready to run
machine = CPUQVM()
machine.run(prog=prog, shots=1000)
counts = machine.result().get_counts()
print(counts)

运行结果:

{'11': 495, '00': 505}

Similarly, the .originir() method on a QProg instance returns the OriginIR string without needing the module-level function:

python
from pyqpanda3.core import QProg, H, CNOT

prog = QProg()
prog << H(0) << CNOT(0, 1)

# These two calls are equivalent:
ir1 = prog.originir()
# ir1 = convert_qprog_to_originir(prog)

print(ir1)

运行结果:

QINIT 2
CREG 1
H q[0]
CNOT q[0],q[1]

Precision and Round-Trip Fidelity ​

The default precision of 8 decimal places is sufficient for most practical circuits. However, when performing round-trip conversion (export then import), floating-point rounding can accumulate errors for circuits with many parameterized gates. The squared matrix distance between the original and round-tripped circuit unitaries should satisfy:

maxi,j|Uijoriginal−Uijround-trip|2<ϵ

where ϵ depends on the precision and circuit depth. For circuits with 10+ rotation gates, use precision=15 to keep ϵ<10−10.

A practical verification pattern:

python
from pyqpanda3.quantum_info import Unitary
from pyqpanda3.intermediate_compiler import (
    convert_qprog_to_qasm,
    convert_qasm_string_to_qprog,
)
import numpy as np

def verify_round_trip(prog, precision=15, tolerance=1e-6):
    """Verify that export-import round-trip preserves circuit unitary."""
    qasm_str = convert_qprog_to_qasm(prog, precision=precision)
    prog2 = convert_qasm_string_to_qprog(qasm_str)

    mat1 = Unitary(prog.to_circuit()).ndarray()
    mat2 = Unitary(prog2.to_circuit()).ndarray()

    # Account for global phase by comparing squared distances
    diff = np.abs(mat1 - mat2) ** 2
    max_error = diff.max()

    if max_error > tolerance:
        raise AssertionError(
            f"Round-trip error {max_error:.2e} exceeds tolerance {tolerance:.2e}"
        )
    return True

Circuit Serialization Patterns ​

A common workflow is to build circuits programmatically, serialize them for storage or transfer, and deserialize them later:

mermaid
flowchart TD
    A["Build QProg"] --> B["Export to IR"]
    B --> C["Store (file/DB)"]
    C --> D["Load IR"]
    D --> E["Import to QProg"]
    E --> F["Execute on simulator"]

    style A fill:#4a90d9,color:#fff,stroke:#2c5f9e
    style B fill:#50c878,color:#fff,stroke:#2d8a4e
    style C fill:#7b68ee,color:#fff,stroke:#4a3db5
    style D fill:#7b68ee,color:#fff,stroke:#4a3db5
    style E fill:#50c878,color:#fff,stroke:#2d8a4e
    style F fill:#f4a460,color:#fff,stroke:#b87333

Pattern 1: OriginIR for internal storage (full fidelity)

python
# Serialize
originir_str = convert_qprog_to_originir(prog, precision=15)
with open("circuit_cache.txt", "w") as f:
    f.write(originir_str)

# Deserialize
prog = convert_originir_file_to_qprog("circuit_cache.txt")
# Clean up the temporary file when done:
# import os; os.remove("circuit_cache.txt")

Pattern 2: QASM for framework interoperability

python
# Serialize for Qiskit consumption
qasm_str = convert_qprog_to_qasm(prog, precision=15)
with open("circuit_for_qiskit.qasm", "w") as f:
    f.write(qasm_str)

# In Qiskit:
# from qiskit import QuantumCircuit
# qc = QuantumCircuit.from_qasm_file("circuit_for_qiskit.qasm")

Pattern 3: Batch conversion

When you have many circuits to convert between formats, use a loop with error handling:

python
from pyqpanda3.intermediate_compiler import (
    convert_qasm_string_to_qprog,
    convert_qprog_to_originir,
)

qasm_circuits = ["<QASM 1>", "<QASM 2>", "<QASM 3>"]  # your QASM strings

for i, qasm_str in enumerate(qasm_circuits):
    try:
        prog = convert_qasm_string_to_qprog(qasm_str)
        originir_str = convert_qprog_to_originir(prog)
        with open(f"circuit_{i:04d}.txt", "w") as f:
            f.write(originir_str)
    except Exception as e:
        print(f"Failed to convert circuit {i}: {e}")

运行结果:

Failed to convert circuit 0: Unknown exception
Failed to convert circuit 1: Unknown exception
Failed to convert circuit 2: Unknown exception

Common Pitfalls ​

  1. Missing header lines in OriginIR: Every OriginIR string must begin with QINIT and CREG declarations. Omitting them causes a parse error.

  2. Wrong precision for round-trips: The default precision of 8 decimal places may lose significant digits in circuits with many rotation gates. Always use precision=15 for round-trip conversions.

  3. Unsupported gates in QASM: Gates like ISWAP, SQISWAP, and RPHI have no QASM 2.0 equivalent. Exporting circuits containing these gates to QASM will fail or produce incorrect results. Use OriginIR instead.

  4. Measurement syntax confusion: Remember that OriginIR uses MEASURE q[i],c[j] (comma), while QASM uses measure q[i] -> c[j] (arrow).

  5. Dynamic circuits in QASM: The QASM 2.0 if(c==val) syntax supports only a limited form of conditional execution. Complex dynamic circuits with QWHILE loops cannot be exported to QASM. Use OriginIR for full dynamic circuit support.

  6. Gate name case sensitivity: OriginIR gate names are uppercase (H, CNOT, RX). QASM gate names are lowercase (h, cx, rx). Mixing the two causes parse errors.

API Reference Summary ​

All functions are imported from pyqpanda3.intermediate_compiler:

python
from pyqpanda3.intermediate_compiler import (
    convert_originir_string_to_qprog,   # str -> QProg
    convert_originir_file_to_qprog,     # filepath -> QProg
    convert_qasm_string_to_qprog,       # str -> QProg
    convert_qasm_file_to_qprog,         # filepath -> QProg
    convert_qprog_to_originir,          # QProg -> str
    convert_qprog_to_qasm,              # QProg -> str
)

Function signatures:

text
convert_qprog_to_originir(prog: QProg, precision: int = 8) -> str
convert_qprog_to_qasm(prog: QProg, precision: int = 8) -> str
convert_originir_string_to_qprog(ir_string: str) -> QProg
convert_originir_file_to_qprog(file_path: str) -> QProg
convert_qasm_string_to_qprog(qasm_string: str) -> QProg
convert_qasm_file_to_qprog(file_path: str) -> QProg

The precision parameter controls the number of decimal places for floating-point values in the exported IR string. It must be a non-negative integer. Higher values preserve more accuracy at the cost of longer string output.

See also: binary communication protocol encoding ​

The functions above are all text-form IR conversions. If you need to encode OriginIR directly into the binary communication protocol byte stream used by the quantum cloud (e.g. for batch submission), use the functions in pyqpanda3.utils, without first building a QProg:

python
from pyqpanda3.utils import CommProtocolConfig, comm_protocol_encode_originir_string

config = CommProtocolConfig()
config.shots = 2048
payload = comm_protocol_encode_originir_string("QINIT 1\nCREG 1\nH q[0]\nMEASURE q[0],c[0]\n", config)
print(type(payload).__name__, len(payload) > 0)   # bytes True

comm_protocol_encode_originir_file(file_path, config) reads OriginIR from a file; comm_protocol_encode([prog], config) encodes from a QProg (equivalent to the OriginIR direct encoding). To recover a program from an instruction JSON stream in the other direction, use pyqpanda3.core.instruction_to_qprog (single stream returns a QProg, multi-stream returns list[QProg]). See the Communication protocol API reference and Cloud Computing tutorial §10.6 for full details.

Released under the MIT License.