Source code for qbraid.passes.qasm.format

# Copyright (C) 2024 qBraid
#
# This file is part of the qBraid-SDK
#
# The qBraid-SDK is free software released under the GNU General Public License v3
# or later. You can redistribute and/or modify it under the terms of the GPL v3.
# See the LICENSE file in the project root or <https://www.gnu.org/licenses/gpl-3.0.html>.
#
# THERE IS NO WARRANTY for the qBraid-SDK, as per Section 15 of the GPL v3.

"""
Module for providing transforamtions to ensure consistency
in the way OpenQASM 3 strings are formatted.

"""

import re
from typing import Optional


def _remove_empty_lines(input_string: str) -> str:
    """Removes all empty lines from the provided string."""
    return "\n".join(line for line in input_string.split("\n") if line.strip())


def _remove_double_empty_lines(qasm: str) -> str:
    """Replace double empty lines with single lines from a QASM string."""
    return re.sub(r"\n\n\n", "\n\n", qasm)


def _remove_comments(qasm: str, keep_header: bool = True) -> str:
    """Remove comments from a QASM string. Optionally keep the header."""
    lines = qasm.splitlines()
    parse_lines = lines[1:] if keep_header else lines
    result_lines = [lines[0]] + [line for line in parse_lines if not line.strip().startswith("//")]
    return "\n".join(result_lines)


def _format_qasm(qasm: str, skip_pattern: str) -> str:
    lines = qasm.split("\n")
    formatted_lines = []

    for line in lines:
        line = line.strip()  # Strip leading and trailing whitespace
        if skip_pattern.match(line) or line.startswith("//"):
            # If the line matches the gate definition pattern, add it as is
            formatted_lines.append(line)
        else:
            # Otherwise, split it at semicolons and add each part as a separate line
            parts = re.split(";[ ]*", line)
            parts = [part + ";" for part in parts if part]  # Remove empty parts
            formatted_lines.extend(parts)

    return "\n".join(formatted_lines)


def format_qasm(qasm: str, skip_pattern: Optional[str] = None) -> str:
    """Format a QASM string."""
    if skip_pattern is not None:
        return _format_qasm(qasm, skip_pattern)

    qasm = _remove_comments(qasm)
    qasm = _remove_double_empty_lines(qasm)
    return qasm.strip()


def _remove_gate_definition(qasm: str, gate_name: str) -> str:
    """Remove a gate definition from a QASM string."""
    lines = iter(qasm.split("\n"))
    new_qasm = ""

    for line in lines:
        if re.search(r"gate\s+(\w+)", line) is not None:
            # extract the gate name
            current_gate_name = re.search(r"gate\s+(\w+)", line).group(1)
            # remove lines from start curly brace to end curly brace
            if current_gate_name == gate_name:
                while "}" not in line:
                    line = next(lines)
            else:
                new_qasm += line + "\n"
        else:
            new_qasm += line + "\n"

    new_qasm = _remove_double_empty_lines(new_qasm)

    return new_qasm.strip()


[docs] def remove_unused_gates(qasm: str) -> str: """Remove unused gate definitions from a QASM string.""" lines = iter(qasm.split("\n")) all_gates = {} for line in lines: if re.search(r"^\s*gate\s+(\w+)", line) is not None: gate_name = re.search(r"^\s*gate\s+(\w+)", line).group(1) all_gates[gate_name] = -1 for gate in all_gates: if re.search(r"\b" + re.escape(gate) + r"\b", line): all_gates[gate] += 1 new_qasm = qasm unused_gates = [gate for gate, count in all_gates.items() if count == 0] for gate in unused_gates: new_qasm = _remove_gate_definition(new_qasm, gate) if len(unused_gates) > 0: return remove_unused_gates(new_qasm) return new_qasm.strip()