Source code for uniqc.qasm.qasm_line_parser

"""OpenQASM 2.0 line parser module.

This module provides regex-based parsing for individual OpenQASM 2.0 lines,
supporting qreg/creg definitions, 1-4 qubit gates, parameterized gates, and measurements.

Key exports:
    OpenQASM2_LineParser: Parser class for individual OpenQASM 2.0 lines.
"""

from __future__ import annotations

__all__ = ["OpenQASM2_LineParser"]

import math
import re
from typing import Any


[docs] class OpenQASM2_LineParser: # noqa: N801 """Parser for individual OpenQASM 2.0 lines. Provides regex-based parsing for OpenQASM 2.0 statements including qreg/creg definitions, 1-4 qubit gates, parameterized gates, and measurements. """ # Fragment patterns identifier: str = r"([A-Za-z_][A-Za-z_\d]*)" blank: str = r" *" comma: str = r"," index: str = r"\[ *(\d+) *\]" any_parameters: str = r"\(([^()]+)\)" # Regex pattern strings regexp_qreg_str: str = "^" + "qreg" + blank + identifier + blank + index + blank + "$" regexp_creg_str: str = "^" + "creg" + blank + identifier + blank + index + blank + "$" qreg_str: str = identifier + blank + index + blank regexp_1q_str: str = "^" + identifier + blank + qreg_str + "$" regexp_2q_str: str = "^" + identifier + blank + qreg_str + comma + blank + qreg_str + "$" regexp_3q_str: str = "^" + identifier + blank + qreg_str + comma + blank + qreg_str + comma + blank + qreg_str + "$" regexp_4q_str: str = ( "^" + identifier + blank + qreg_str + comma + blank + qreg_str + comma + blank + qreg_str + comma + blank + qreg_str + "$" ) regexp_1qnp_str: str = "^" + identifier + blank + any_parameters + blank + qreg_str + "$" regexp_2qnp_str: str = "^" + identifier + blank + any_parameters + blank + qreg_str + comma + blank + qreg_str + "$" regexp_3qnp_str: str = ( "^" + identifier + blank + any_parameters + blank + qreg_str + comma + blank + qreg_str + comma + blank + qreg_str + "$" ) regexp_measure_str: str = "^" + "measure" + blank + qreg_str + "->" + blank + qreg_str + "$" # Compiled regex objects regexp_qreg: re.Pattern[str] = re.compile(regexp_qreg_str) regexp_creg: re.Pattern[str] = re.compile(regexp_creg_str) regexp_1q: re.Pattern[str] = re.compile(regexp_1q_str) regexp_2q: re.Pattern[str] = re.compile(regexp_2q_str) regexp_3q: re.Pattern[str] = re.compile(regexp_3q_str) regexp_4q: re.Pattern[str] = re.compile(regexp_4q_str) regexp_1qnp: re.Pattern[str] = re.compile(regexp_1qnp_str) regexp_2qnp: re.Pattern[str] = re.compile(regexp_2qnp_str) regexp_3qnp: re.Pattern[str] = re.compile(regexp_3qnp_str) regexp_measure: re.Pattern[str] = re.compile(regexp_measure_str) def __init__(self) -> None: ...
[docs] @staticmethod def handle_qreg(line: str) -> tuple[str, int]: """Parse a qreg definition line. Args: line: QASM line defining a quantum register. Returns: tuple: (register_name, size) """ matches = OpenQASM2_LineParser.regexp_qreg.match(line) qreg_name: str = matches.group(1) # type: ignore[union-attr] qreg_size: int = int(matches.group(2)) # type: ignore[union-attr] return qreg_name, qreg_size
[docs] @staticmethod def handle_creg(line: str) -> tuple[str, int]: """Parse a creg definition line. Args: line: QASM line defining a classical register. Returns: tuple: (register_name, size) """ matches = OpenQASM2_LineParser.regexp_creg.match(line) creg_name: str = matches.group(1) # type: ignore[union-attr] creg_size: int = int(matches.group(2)) # type: ignore[union-attr] return creg_name, creg_size
[docs] @staticmethod def handle_parameters(parameters_str: str) -> list[float]: """Parse a parameter string into a list of floats. Args: parameters_str: Comma-separated parameter expression string. Returns: list[float]: List of parsed parameter values. """ parameters_str = parameters_str.strip() parameter_str_list = parameters_str.split(",") parameters: list[float] = [] for parameter_str in parameter_str_list: parameters.append(float(eval(parameter_str.strip(), {"pi": math.pi}))) # noqa: PGH001, S307 return parameters
[docs] @staticmethod def handle_1q(line: str) -> tuple[str, str, int]: """Parse a 1-qubit gate line. Returns: tuple: (op_name, qreg_name, qubit_index) """ matches = OpenQASM2_LineParser.regexp_1q.match(line) op_name: str = matches.group(1) # type: ignore[union-attr] qreg_name: str = matches.group(2) # type: ignore[union-attr] qubit_index: int = int(matches.group(3)) # type: ignore[union-attr] return op_name, qreg_name, qubit_index
[docs] @staticmethod def handle_2q(line: str) -> tuple[str, str, int, str, int]: """Parse a 2-qubit gate line. Returns: tuple: (op_name, qreg_name1, qubit_index1, qreg_name2, qubit_index2) """ matches = OpenQASM2_LineParser.regexp_2q.match(line) op_name: str = matches.group(1) # type: ignore[union-attr] qreg_name1: str = matches.group(2) # type: ignore[union-attr] qubit_index1: int = int(matches.group(3)) # type: ignore[union-attr] qreg_name2: str = matches.group(4) # type: ignore[union-attr] qubit_index2: int = int(matches.group(5)) # type: ignore[union-attr] return op_name, qreg_name1, qubit_index1, qreg_name2, qubit_index2
[docs] @staticmethod def handle_3q( line: str, ) -> tuple[str, str, int, str, int, str, int]: """Parse a 3-qubit gate line. Returns: tuple: (op_name, qreg_name1, qubit_index1, qreg_name2, qubit_index2, qreg_name3, qubit_index3) """ matches = OpenQASM2_LineParser.regexp_3q.match(line) op_name: str = matches.group(1) # type: ignore[union-attr] qreg_name1: str = matches.group(2) # type: ignore[union-attr] qubit_index1: int = int(matches.group(3)) # type: ignore[union-attr] qreg_name2: str = matches.group(4) # type: ignore[union-attr] qubit_index2: int = int(matches.group(5)) # type: ignore[union-attr] qreg_name3: str = matches.group(6) # type: ignore[union-attr] qubit_index3: int = int(matches.group(7)) # type: ignore[union-attr] return (op_name, qreg_name1, qubit_index1, qreg_name2, qubit_index2, qreg_name3, qubit_index3)
[docs] @staticmethod def handle_4q( line: str, ) -> tuple[str, str, int, str, int, str, int, str, int]: """Parse a 4-qubit gate line. Returns: tuple: (op_name, qreg_name1, qubit_index1, qreg_name2, qubit_index2, qreg_name3, qubit_index3, qreg_name4, qubit_index4) """ matches = OpenQASM2_LineParser.regexp_4q.match(line) op_name: str = matches.group(1) # type: ignore[union-attr] qreg_name1: str = matches.group(2) # type: ignore[union-attr] qubit_index1: int = int(matches.group(3)) # type: ignore[union-attr] qreg_name2: str = matches.group(4) # type: ignore[union-attr] qubit_index2: int = int(matches.group(5)) # type: ignore[union-attr] qreg_name3: str = matches.group(6) # type: ignore[union-attr] qubit_index3: int = int(matches.group(7)) # type: ignore[union-attr] qreg_name4: str = matches.group(8) # type: ignore[union-attr] qubit_index4: int = int(matches.group(9)) # type: ignore[union-attr] return ( op_name, qreg_name1, qubit_index1, qreg_name2, qubit_index2, qreg_name3, qubit_index3, qreg_name4, qubit_index4, )
[docs] @staticmethod def handle_1qnp(line: str, n_parameters: int) -> tuple[str, list[float], str, int]: """Parse a 1-qubit n-parameter gate line. Args: line: QASM line. n_parameters: Expected number of parameters. Returns: tuple: (op_name, parameters, qreg_name, qubit_index) """ matches = OpenQASM2_LineParser.regexp_1qnp.match(line) op_name: str = matches.group(1) # type: ignore[union-attr] parameters: list[float] = OpenQASM2_LineParser.handle_parameters(matches.group(2)) # type: ignore[union-attr] qreg_name: str = matches.group(3) # type: ignore[union-attr] qubit_index: int = int(matches.group(4)) # type: ignore[union-attr] if len(parameters) != n_parameters: raise ValueError( f"The number of parameters for {op_name} should be {n_parameters}, but got {len(parameters)}." ) return op_name, parameters, qreg_name, qubit_index
[docs] @staticmethod def handle_2qnp(line: str, n_parameters: int) -> tuple[str, list[float], str, int, str, int]: """Parse a 2-qubit n-parameter gate line. Args: line: QASM line. n_parameters: Expected number of parameters. Returns: tuple: (op_name, parameters, qreg_name1, qubit_index1, qreg_name2, qubit_index2) """ matches = OpenQASM2_LineParser.regexp_2qnp.match(line) op_name: str = matches.group(1) # type: ignore[union-attr] parameters: list[float] = OpenQASM2_LineParser.handle_parameters(matches.group(2)) # type: ignore[union-attr] qreg_name1: str = matches.group(3) # type: ignore[union-attr] qubit_index1: int = int(matches.group(4)) # type: ignore[union-attr] qreg_name2: str = matches.group(5) # type: ignore[union-attr] qubit_index2: int = int(matches.group(6)) # type: ignore[union-attr] if len(parameters) != n_parameters: raise ValueError( f"The number of parameters for {op_name} should be {n_parameters}, but got {len(parameters)}." ) return op_name, parameters, qreg_name1, qubit_index1, qreg_name2, qubit_index2
[docs] @staticmethod def handle_3qnp(line: str, n_parameters: int) -> tuple[str, list[float], str, int, str, int, str, int]: """Parse a 3-qubit n-parameter gate line. Args: line: QASM line. n_parameters: Expected number of parameters. Returns: tuple: (op_name, parameters, qreg_name1, qubit_index1, qreg_name2, qubit_index2, qreg_name3, qubit_index3) """ matches = OpenQASM2_LineParser.regexp_3qnp.match(line) op_name: str = matches.group(1) # type: ignore[union-attr] parameters: list[float] = OpenQASM2_LineParser.handle_parameters(matches.group(2)) # type: ignore[union-attr] qreg_name1: str = matches.group(3) # type: ignore[union-attr] qubit_index1: int = int(matches.group(4)) # type: ignore[union-attr] qreg_name2: str = matches.group(5) # type: ignore[union-attr] qubit_index2: int = int(matches.group(6)) # type: ignore[union-attr] qreg_name3: str = matches.group(7) # type: ignore[union-attr] qubit_index3: int = int(matches.group(8)) # type: ignore[union-attr] if len(parameters) != n_parameters: raise ValueError( f"The number of parameters for {op_name} should be {n_parameters}, but got {len(parameters)}." ) return ( op_name, parameters, qreg_name1, qubit_index1, qreg_name2, qubit_index2, qreg_name3, qubit_index3, )
[docs] @staticmethod def handle_1q1p(line: str) -> tuple[str, list[float], str, int]: """Parse a 1-qubit 1-parameter gate line. Returns: tuple: (op_name, parameters, qreg_name, qubit_index) """ return OpenQASM2_LineParser.handle_1qnp(line, 1)
[docs] @staticmethod def handle_1q2p(line: str) -> tuple[str, list[float], str, int]: """Parse a 1-qubit 2-parameter gate line. Returns: tuple: (op_name, parameters, qreg_name, qubit_index) """ return OpenQASM2_LineParser.handle_1qnp(line, 2)
[docs] @staticmethod def handle_1q3p(line: str) -> tuple[str, list[float], str, int]: """Parse a 1-qubit 3-parameter gate line. Returns: tuple: (op_name, parameters, qreg_name, qubit_index) """ return OpenQASM2_LineParser.handle_1qnp(line, 3)
[docs] @staticmethod def handle_1q4p(line: str) -> tuple[str, list[float], str, int]: """Parse a 1-qubit 4-parameter gate line. Returns: tuple: (op_name, parameters, qreg_name, qubit_index) """ return OpenQASM2_LineParser.handle_1qnp(line, 4)
[docs] @staticmethod def handle_2q1p(line: str) -> tuple[str, list[float], str, int, str, int]: """Parse a 2-qubit 1-parameter gate line. Returns: tuple: (op_name, parameters, qreg_name1, qubit_index1, qreg_name2, qubit_index2) """ return OpenQASM2_LineParser.handle_2qnp(line, 1)
[docs] @staticmethod def handle_2q2p(line: str) -> tuple[str, list[float], str, int, str, int]: """Parse a 2-qubit 2-parameter gate line. Returns: tuple: (op_name, parameters, qreg_name1, qubit_index1, qreg_name2, qubit_index2) """ return OpenQASM2_LineParser.handle_2qnp(line, 2)
[docs] @staticmethod def handle_2q3p(line: str) -> tuple[str, list[float], str, int, str, int]: """Parse a 2-qubit 3-parameter gate line. Returns: tuple: (op_name, parameters, qreg_name1, qubit_index1, qreg_name2, qubit_index2) """ return OpenQASM2_LineParser.handle_2qnp(line, 3)
[docs] @staticmethod def handle_2q4p(line: str) -> tuple[str, list[float], str, int, str, int]: """Parse a 2-qubit 4-parameter gate line. Returns: tuple: (op_name, parameters, qreg_name1, qubit_index1, qreg_name2, qubit_index2) """ return OpenQASM2_LineParser.handle_2qnp(line, 4)
[docs] @staticmethod def handle_3q1p(line: str) -> tuple[str, list[float], str, int, str, int, str, int]: """Parse a 3-qubit 1-parameter gate line. Returns: tuple: (op_name, parameters, qreg_name1, qubit_index1, qreg_name2, qubit_index2, qreg_name3, qubit_index3) """ return OpenQASM2_LineParser.handle_3qnp(line, 1)
[docs] @staticmethod def handle_3q2p(line: str) -> tuple[str, list[float], str, int, str, int, str, int]: """Parse a 3-qubit 2-parameter gate line. Returns: tuple: (op_name, parameters, qreg_name1, qubit_index1, qreg_name2, qubit_index2, qreg_name3, qubit_index3) """ return OpenQASM2_LineParser.handle_3qnp(line, 2)
[docs] @staticmethod def handle_3q3p(line: str) -> tuple[str, list[float], str, int, str, int, str, int]: """Parse a 3-qubit 3-parameter gate line. Returns: tuple: (op_name, parameters, qreg_name1, qubit_index1, qreg_name2, qubit_index2, qreg_name3, qubit_index3) """ return OpenQASM2_LineParser.handle_3qnp(line, 3)
[docs] @staticmethod def handle_3q4p(line: str) -> tuple[str, list[float], str, int, str, int, str, int]: """Parse a 3-qubit 4-parameter gate line. Returns: tuple: (op_name, parameters, qreg_name1, qubit_index1, qreg_name2, qubit_index2, qreg_name3, qubit_index3) """ return OpenQASM2_LineParser.handle_3qnp(line, 4)
[docs] @staticmethod def handle_measure(line: str) -> tuple[str, int, str, int]: """Parse a MEASURE statement line. Args: line: QASM measure statement. Returns: tuple: (qreg_name, qubit_index, creg_name, creg_index) """ matches = OpenQASM2_LineParser.regexp_measure.match(line) qreg_name: str = matches.group(1) # type: ignore[union-attr] qubit_index: int = int(matches.group(2)) # type: ignore[union-attr] creg_name: str = matches.group(3) # type: ignore[union-attr] creg_index: int = int(matches.group(4)) # type: ignore[union-attr] return qreg_name, qubit_index, creg_name, creg_index
[docs] @staticmethod def parse_line( line: str, ) -> tuple[str | None, tuple[str, int] | list[tuple[str, int]] | None, tuple[str, int] | None, Any]: """Parse a single line of OpenQASM 2 code. Returns: operation: Gate name string or None q: Qubit spec — (qreg_name, qubit_index) for 1q, list for multi-q, or None c: Classical bit spec — (creg_name, creg_index) or None parameter: Parameter list (from handle_parameters) or None """ try: q: tuple[str, int] | list[tuple[str, int]] | None = None c: tuple[str, int] | None = None operation: str | None = None parameter: Any = None # remove comments and whitespace if not line: return q, c, operation, parameter if line.startswith("//"): return q, c, operation, parameter # extract operation if "(" in line: # noqa: SIM108 operation = line.split("(")[0].strip() else: operation = line.split()[0].strip() if operation == "qreg": qreg_name, qreg_size = OpenQASM2_LineParser.handle_qreg(line) q = (qreg_name, qreg_size) elif operation == "creg": creg_name, creg_size = OpenQASM2_LineParser.handle_creg(line) c = (creg_name, creg_size) elif operation == "//": pass elif ( operation == "id" or operation == "h" or operation == "x" or operation == "y" or operation == "z" or operation == "s" or operation == "sdg" or operation == "sx" or operation == "sxdg" or operation == "t" or operation == "tdg" ): operation, qreg_name, qubit_index = OpenQASM2_LineParser.handle_1q(line) q = (qreg_name, qubit_index) elif operation in ("cx", "cy", "cz", "swap", "ch"): ( operation, qreg_name1, qubit_index1, qreg_name2, qubit_index2, ) = OpenQASM2_LineParser.handle_2q(line) q = [(qreg_name1, qubit_index1), (qreg_name2, qubit_index2)] elif operation in ("ccx", "cswap"): ( operation, qreg_name1, qubit_index1, qreg_name2, qubit_index2, qreg_name3, qubit_index3, ) = OpenQASM2_LineParser.handle_3q(line) q = [ (qreg_name1, qubit_index1), (qreg_name2, qubit_index2), (qreg_name3, qubit_index3), ] elif operation == "c3x": ( operation, qreg_name1, qubit_index1, qreg_name2, qubit_index2, qreg_name3, qubit_index3, qreg_name4, qubit_index4, ) = OpenQASM2_LineParser.handle_4q(line) q = [ (qreg_name1, qubit_index1), (qreg_name2, qubit_index2), (qreg_name3, qubit_index3), (qreg_name4, qubit_index4), ] elif operation in ("rx", "ry", "rz", "u1"): operation, parameter, qreg_name, qubit_index = OpenQASM2_LineParser.handle_1q1p(line) # type: ignore[assignment] q = (qreg_name, qubit_index) elif operation == "u2": operation, parameter, qreg_name, qubit_index = OpenQASM2_LineParser.handle_1q2p(line) # type: ignore[assignment] q = (qreg_name, qubit_index) elif operation == "u0": raise NotImplementedError(f"This line of OpenQASM 2 has not been supported yet: {line}.") elif operation in ("u3", "u"): operation, parameter, qreg_name, qubit_index = OpenQASM2_LineParser.handle_1q3p(line) # type: ignore[assignment] q = (qreg_name, qubit_index) elif operation in ("rxx", "ryy", "rzz", "cu1", "crx", "cry", "crz"): ( operation, parameter, qreg_name1, qubit_index1, qreg_name2, qubit_index2, ) = OpenQASM2_LineParser.handle_2q1p(line) # type: ignore[assignment] q = [(qreg_name1, qubit_index1), (qreg_name2, qubit_index2)] elif operation == "cu3": ( operation, parameter, qreg_name1, qubit_index1, qreg_name2, qubit_index2, ) = OpenQASM2_LineParser.handle_2q3p(line) # type: ignore[assignment] q = [(qreg_name1, qubit_index1), (qreg_name2, qubit_index2)] elif operation == "barrier": pass elif operation == "measure": qreg_name, qubit_index, creg_name, creg_index = OpenQASM2_LineParser.handle_measure(line) operation = "measure" # type: ignore[assignment] q = (qreg_name, qubit_index) c = (creg_name, creg_index) else: raise NotImplementedError(f"This line of OpenQASM 2 has not been supported yet: {line}.") return operation, q, c, parameter except AttributeError as e: raise RuntimeError(f"Error when parsing the line: {line}") from e
if __name__ == "__main__": print("----------qreg test------------") matches = OpenQASM2_LineParser.regexp_qreg.match("qreg q [ 12 ]") print(matches.group(0)) print(matches.group(1)) print(matches.group(2)) print("----------creg test------------") matches = OpenQASM2_LineParser.regexp_creg.match("creg c [ 12 ]") print(matches.group(0)) print(matches.group(1)) print(matches.group(2)) print("----------op 1q test-----------") matches = OpenQASM2_LineParser.regexp_1q.match("h q[0]") print(matches.group(0)) print(matches.group(1)) print(matches.group(2)) print(matches.group(3)) print("----------op 2q test-----------") matches = OpenQASM2_LineParser.regexp_2q.match("cx q[0],q[12]") print(matches.group(0)) print(matches.group(1)) print(matches.group(2)) print(matches.group(3)) print(matches.group(4)) print(matches.group(5)) print("----------op 3q test-----------") matches = OpenQASM2_LineParser.regexp_3q.match("ccx q[0],q[12],q[11]") print(matches.group(0)) for i in range(1, 8): print(matches.group(i)) print("----------op 1q1p test---------") matches = OpenQASM2_LineParser.regexp_1qnp.match("ry (-0.5*pi) q[0]") print(matches.group(0)) print(matches.group(1)) print(matches.group(2)) print(matches.group(3)) print(matches.group(4)) results = OpenQASM2_LineParser.handle_1q1p("ry (-0.5*pi) q[0]") print(results) print("----------op 1q2p test---------") matches = OpenQASM2_LineParser.regexp_1qnp.match("u2 (-0.5*pi, 11) q[0]") print(matches.group(0)) print(matches.group(1)) print(matches.group(2)) print(matches.group(3)) print(matches.group(4)) results = OpenQASM2_LineParser.handle_1q2p("u2 (-0.5*pi, 11) q[0]") print(results) print("----------op 1q3p test---------") matches = OpenQASM2_LineParser.regexp_1qnp.match("u3 (-0.5*pi, 11, 888.1111) q[0]") print(matches.group(0)) print(matches.group(1)) print(matches.group(2)) print(matches.group(3)) print(matches.group(4)) results = OpenQASM2_LineParser.handle_1q3p("u3 (-0.5*pi, 11, 888.1111) q[0]") print(results) print("----------op 2q1p test---------") matches = OpenQASM2_LineParser.regexp_2qnp.match("rxx (-0.5*pi) q[0], q[108]") print(matches.group(0)) print(matches.group(1)) print(matches.group(2)) print(matches.group(3)) print(matches.group(4)) results = OpenQASM2_LineParser.handle_2q1p("rxx (-0.5*pi) q[0], q[108]") print(results) results = OpenQASM2_LineParser.handle_2q1p("rzz(0.8342297582553907) q[2],q[0] ") print(results) print("----------measure test---------") matches = OpenQASM2_LineParser.regexp_measure.match("measure q[0] -> c[18]") print(matches.group(0)) print(matches.group(1)) print(matches.group(2)) print(matches.group(3)) print(matches.group(4))