"""OriginIR base parser module.
This module provides the base parser for OriginIR quantum circuit representation,
including parsing QINIT, CREG statements and quantum operations.
Key exports:
OriginIR_BaseParser: Base parser class for OriginIR circuits.
"""
__all__ = ["OriginIR_BaseParser"]
from copy import deepcopy
from uniqc.circuit_builder import opcode_to_line_originir
from uniqc.circuit_builder.qcircuit import Circuit
from .originir_line_parser import OriginIR_LineParser
[docs]
class OriginIR_BaseParser:
"""Parser for OriginIR quantum circuit representation.
Attributes:
n_qubit: Number of qubits.
n_cbit: Number of classical bits.
program_body: List of operation opcodes.
raw_originir: Raw OriginIR string.
measure_qubits: List of measurement tuples (qubit, cbit).
"""
def __init__(self):
self.n_qubit = None
self.n_cbit = None
self.program_body = []
self.raw_originir = None
self.measure_qubits: list[tuple[int, int]] = []
def _extract_qinit_statement(self, lines):
for i, line in enumerate(lines):
operation, q, c, parameter, dagger_flag, control_qubits = OriginIR_LineParser.parse_line(line.strip())
if operation is None:
continue
if operation != "QINIT":
raise ValueError("OriginIR input does not have correct QINIT statement.")
self.n_qubit = q
# skip this line
return i + 1
def _extract_creg_statement(self, lines, start_lineno):
for i in range(start_lineno, len(lines)):
operation, q, c, parameter, dagger_flag, control_qubits = OriginIR_LineParser.parse_line(lines[i].strip())
if operation is None:
continue
if operation != "CREG":
raise ValueError("OriginIR input does not have correct CREG statement.")
self.n_cbit = c
# skip this line
return i + 1
[docs]
def parse(self, originir_str):
"""Parse an OriginIR string and populate internal state.
Args:
originir_str: OriginIR string to parse.
Returns:
Circuit: A uniqc Circuit object.
"""
self.raw_originir = originir_str
# Split into lines, and use strip() to skip the blank lines
lines = originir_str.strip().splitlines()
if not lines:
raise ValueError("Parse error. Input is empty.")
# Extract the QINIT statement and CBIT statement
# Note: always return current line number (current_lineno)
# Will raise ValueError when the statement does not follow the grammar.
# The remaining part will not have QINIT and CBIT statements
current_lineno = self._extract_qinit_statement(lines)
current_lineno = self._extract_creg_statement(lines, current_lineno)
control_qubits_set = set()
dagger_count = 0
dagger_stack = []
for lineno in range(current_lineno, len(lines)):
# handle the line
line = lines[lineno]
operation, qubits, cbit, parameter, dagger_flag, control_qubits = OriginIR_LineParser.parse_line(
line.strip()
)
if operation is None:
continue
# check if the operational qubit and cbit are within range
if isinstance(qubits, list):
for qubit in qubits:
if qubit >= self.n_qubit:
raise ValueError(
f"Parse error at line {lineno}: {line}\nQubit exceeds the maximum (QINIT {self.n_qubit})."
)
elif qubits:
if qubits >= self.n_qubit:
raise ValueError(
f"Parse error at line {lineno}: {line}\nQubit exceeds the maximum (QINIT {self.n_qubit})."
)
else:
# Dagger statement does not contain qubit parameter
pass
if cbit and cbit >= self.n_cbit:
raise ValueError(
f"Parse error at line {lineno}: {line}\nCbit exceeds the maximum (CBIT {self.n_cbit})."
)
# Handle the control statement
if operation == "CONTROL":
# Add all control qubits to the set
control_qubits_set.update(qubits)
elif operation == "ENDCONTROL":
for qubit in qubits:
control_qubits_set.discard(qubit)
# Handle the dagger statement
elif operation == "DAGGER":
# Add a new list to the stack to collect operations inside this DAGGER block
dagger_stack.append([])
dagger_count += 1
elif operation == "ENDDAGGER":
# Pop the latest list of operations from the dagger stack and reverse them
if dagger_stack:
reversed_ops = dagger_stack.pop()
# case 1: dagger_stack is empty, insert reversed operations
if not dagger_stack:
self.program_body.extend(reversed_ops[::-1])
# case 2: dagger_stack is not empty, insert reversed operations to top
else:
dagger_stack[-1].extend(reversed_ops[::-1])
else:
raise ValueError(
f"Parse error at line {lineno}: {line}\nEncounter ENDDAGGER operation before any DAGGER."
)
dagger_count -= 1
# Handle the common statements
else:
# Handle the measure statement
if operation == "MEASURE":
if control_qubits_set:
raise ValueError(
f"Parse error at line {lineno}: {line}\nMEASURE operation is inside a CONTROL block."
)
if dagger_stack:
raise ValueError(
f"Parse error at line {lineno}: {line}\nMEASURE operation is inside a DAGGER block."
)
# Add the measurement to the list of measurements
self.measure_qubits.append((qubits, cbit))
else:
# For common statements (gates)
dagger_flag = dagger_flag ^ bool(dagger_count % 2)
ctrl_qubits = deepcopy(control_qubits_set)
# Add the control qubits to the set of control qubits
for qubit in control_qubits:
if qubit in ctrl_qubits:
raise ValueError(
f"Parse error at line {lineno}: {line}\n"
f"Qubit {qubit} is duplicated in the CONTROL statement."
)
ctrl_qubits.add(qubit)
# check whether qubits and ctrl_qubit have duplicates
qubits_used = deepcopy(ctrl_qubits)
if isinstance(qubits, int):
if qubits in ctrl_qubits:
raise ValueError(
f"Parse error at line {lineno}: {line}\n"
f"Qubit {qubits} is duplicated in the CONTROL statement."
)
else:
for qubit in qubits:
if qubit in ctrl_qubits:
raise ValueError(
f"Parse error at line {lineno}: {line}\n"
f"Qubit {qubit} is duplicated in the CONTROL statement."
)
qubits_used.add(qubit)
if dagger_stack:
# insert to the top of the dagger stack
ctrl_list = sorted(ctrl_qubits) if ctrl_qubits else None
dagger_stack[-1].append((operation, qubits, cbit, parameter, dagger_flag, ctrl_list))
else:
ctrl_list = sorted(ctrl_qubits) if ctrl_qubits else None
self.program_body.append((operation, qubits, cbit, parameter, dagger_flag, ctrl_list))
# Finally, check if all dagger and control operations are closed
if control_qubits_set:
raise ValueError("Parse error at end.\nThe CONTROL operation is not closed at the end of the OriginIR.")
if dagger_stack:
raise ValueError("Parse error at end.\nThe DAGGER operation is not closed at the end of the OriginIR.")
[docs]
def to_extended_originir(self):
"""Convert parsed data back to extended OriginIR string.
Returns:
str: Extended OriginIR string representation.
"""
ret = f"QINIT {self.n_qubit}\n"
ret += f"CREG {self.n_cbit}\n"
body_lines = [opcode_to_line_originir(opcode) for opcode in self.program_body]
ret += "\n".join(body_lines)
if body_lines:
ret += "\n"
for qubit, cbit in sorted(self.measure_qubits, key=lambda item: item[1]):
ret += f"MEASURE q[{qubit}], c[{cbit}]\n"
return ret
@property
def originir(self):
"""OriginIR string representation (alias for to_extended_originir).
Returns:
str: Extended OriginIR string.
"""
return self.to_extended_originir()
def __str__(self):
return self.to_extended_originir()
[docs]
def to_circuit(self) -> Circuit:
"""
The function coverts OriginIR string into uniqc.Circuit object.
Returns:
uniqc.Circuit object.
"""
circuit = Circuit()
for opcode in self.program_body:
operation, qubits, cbit, parameter, dagger_flag, control_qubits = opcode
circuit.add_gate(operation, qubits, cbit, parameter, dagger_flag, control_qubits)
if self.measure_qubits:
measured_qubits = [qubit for qubit, _ in sorted(self.measure_qubits, key=lambda item: item[1])]
circuit.measure(*measured_qubits)
if self.n_cbit is not None:
circuit.cbit_num = self.n_cbit
return circuit
[docs]
def to_qasm(self):
"""
The function coverts OriginIR string into OpenQASM string.
Returns:
OpenQASM string.
"""
circuit = self.to_circuit()
return circuit.qasm