File size: 2,632 Bytes
cc0edce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
from __future__ import annotations

import csv
import io
from typing import Any, Dict, Optional, Tuple


def default_qubit_coords(n_qubits: int, rows: int, cols: int) -> Dict[int, Tuple[int, int]]:
    n = int(n_qubits)
    rmax = int(rows)
    cmax = int(cols)
    m: Dict[int, Tuple[int, int]] = {}
    for q in range(n):
        rr = q // max(1, cmax)
        cc = q % max(1, cmax)
        if rr >= rmax:
            break
        m[q] = (rr, cc)
    return m


def _to_int(v: Any) -> Optional[int]:
    try:
        s = str(v).strip()
        if s == "":
            return None
        return int(s)
    except Exception:
        return None


def parse_layout_csv_text(
    csv_text: str,
    n_qubits: int,
    *,
    rows: int,
    cols: int,
) -> Tuple[Dict[int, Tuple[int, int]], Dict[str, Any]]:
    """
    Parse qubit-to-layout CSV.
    Expected headers (case-insensitive):
      qubit,row,col
    Also accepts aliases:
      qubit_id/q/id, r/y for row, c/x/column for col.
    """
    n = int(n_qubits)
    rmax = int(rows)
    cmax = int(cols)
    fallback = default_qubit_coords(n, rmax, cmax)

    text = (csv_text or "").strip()
    if not text:
        return fallback, {
            "source": "default",
            "parsed_rows": 0,
            "mapped": len(fallback),
            "fallback": len(fallback),
            "skipped": 0,
            "duplicates": 0,
        }

    reader = csv.DictReader(io.StringIO(text))
    coords: Dict[int, Tuple[int, int]] = {}
    skipped = 0
    duplicates = 0
    parsed_rows = 0

    def _pick(row: Dict[str, Any], *keys: str) -> Any:
        row_lc = {str(k).strip().lower(): v for k, v in row.items()}
        for k in keys:
            if k in row_lc:
                return row_lc[k]
        return None

    for raw in reader:
        parsed_rows += 1
        q = _to_int(_pick(raw, "qubit", "qubit_id", "q", "id"))
        rr = _to_int(_pick(raw, "row", "r", "y"))
        cc = _to_int(_pick(raw, "col", "column", "c", "x"))
        if q is None or rr is None or cc is None:
            skipped += 1
            continue
        if not (0 <= q < n):
            skipped += 1
            continue
        if not (0 <= rr < rmax and 0 <= cc < cmax):
            skipped += 1
            continue
        if q in coords:
            duplicates += 1
        coords[q] = (rr, cc)

    merged = dict(fallback)
    merged.update(coords)
    return merged, {
        "source": "uploaded",
        "parsed_rows": parsed_rows,
        "mapped": len(coords),
        "fallback": len(merged) - len(coords),
        "skipped": skipped,
        "duplicates": duplicates,
    }