Source code for uniqc.originir.originir_base_parser

"""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