prism-q 0.35.0

Fast Rust quantum circuit simulator. OpenQASM 3.0, multiple backends, AVX2 SIMD kernels, optional CUDA and MPI, QEC tooling, Python bindings.
Documentation
import argparse
from collections import defaultdict
from pathlib import Path
import re
import subprocess


def read_lcov(path, root):
    files = defaultdict(dict)
    current = None
    for record in path.read_text(encoding="utf-8").splitlines():
        if record.startswith("SF:"):
            source = Path(record[3:].replace("\\", "/"))
            source = (root / source).resolve()
            current = source.relative_to(root).as_posix() if source.is_relative_to(root) else None
            if current and not current.startswith("src/"):
                current = None
        elif record.startswith("DA:") and current:
            line, hits, *_ = record[3:].split(",")
            number = int(line)
            files[current][number] = files[current].get(number, 0) + int(hits)
        elif record == "end_of_record":
            current = None
    if not any(files.values()):
        raise ValueError("LCOV contains no src/ line records for this checkout")
    return dict(files)


def changed_lines(diff):
    changes = defaultdict(set)
    current = None
    for line in diff.splitlines():
        if line.startswith("+++ "):
            current = line[6:] if line.startswith("+++ b/src/") else None
        elif current and line.startswith("@@ "):
            match = re.match(r"@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", line)
            if match:
                first = int(match[1])
                count = int(match[2]) if match[2] is not None else 1
                changes[current].update(range(first, first + count))
    return dict(changes)


def line_ranges(lines):
    groups = []
    for line in sorted(lines):
        if groups and groups[-1][1] + 1 == line:
            groups[-1][1] = line
        else:
            groups.append([line, line])
    return ", ".join(str(a) if a == b else f"{a}-{b}" for a, b in groups)


def render_report(files, changes=None):
    subsystems = defaultdict(lambda: [0, 0])
    for name, lines in files.items():
        parts = name.split("/")
        subsystem = "/".join(parts[:3] if parts[1] == "backend" else parts[:2])
        subsystems[subsystem][0] += sum(hits > 0 for hits in lines.values())
        subsystems[subsystem][1] += len(lines)
    report = [
        "## Source line coverage",
        "",
        "Instrumented src/ lines, including inline tests. Feature-excluded files are not measured.",
        "Line hits do not establish branch coverage or assertion strength.",
        "",
        "| Subsystem | Hit / measured | Coverage |",
        "| --- | ---: | ---: |",
    ]
    for name, (hit, total) in sorted(subsystems.items(), key=lambda item: item[1][0] - item[1][1]):
        report.append(f"| {name} | {hit} / {total} | {100 * hit / total:.1f}% |")
    report.extend([
        "", "### Files with most uncovered lines", "",
        "| File | Uncovered | First 30 uncovered lines |", "| --- | ---: | --- |",
    ])
    gaps = {name: sorted(line for line, hits in lines.items() if hits == 0) for name, lines in files.items()}
    for name in sorted(gaps, key=lambda name: (-len(gaps[name]), name))[:20]:
        if gaps[name]:
            report.append(f"| {name} | {len(gaps[name])} | {line_ranges(gaps[name][:30])} |")
    if changes is not None:
        report.extend([
            "", "### Changed lines", "",
            "Unmapped lines may be comments, non-executable declarations, or excluded code.",
            "",
            "| File | Hit / measured changes | Uncovered changes | Unmapped changes |",
            "| --- | ---: | --- | ---: |",
        ])
        for name, lines in sorted(changes.items()):
            if not lines:
                continue
            measured = lines & files.get(name, {}).keys()
            uncovered = {line for line in measured if files[name][line] == 0}
            report.append(
                f"| {name} | {len(measured) - len(uncovered)} / {len(measured)} | "
                f"{line_ranges(uncovered) or '-'} | {len(lines - measured)} |"
            )
        if not any(changes.values()):
            report.append("No changed src/ lines.")
    return "\n".join(report) + "\n"


def main():
    parser = argparse.ArgumentParser(description="Summarize LCOV gaps by subsystem and changed line.")
    parser.add_argument("lcov", type=Path)
    parser.add_argument("--base", help="Git revision to compare against the current checkout.")
    args = parser.parse_args()
    root = Path(__file__).resolve().parents[1]
    changes = None
    if args.base:
        diff = subprocess.check_output(
            ["git", "-c", "core.quotePath=false", "diff", "--no-ext-diff", "--no-renames",
             "--unified=0", args.base, "--", "src"],
            cwd=root, text=True, encoding="utf-8",
        )
        changes = changed_lines(diff)
    print(render_report(read_lcov(args.lcov, root), changes), end="")


if __name__ == "__main__":
    main()