File size: 3,879 Bytes
eef9104
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# *******************************************************************************
# Copyright (c) 2026 Ahmad Ali Parr and others.
#
# This program and the accompanying materials are made available under the
# terms of the CPSC Eclipse Strict Copyleft License, Version 1.0, which is
# available in LICENSES/CPSC-ESCL-1.0.txt, or, at your option, under the
# GNU Affero General Public License, Version 3 only, which is available in
# LICENSES/AGPL-3.0.txt. Both options are strict copyleft. There is no
# classpath exception and no permissive relicensing.
#
# SPDX-License-Identifier: LicenseRef-CPSC-ESCL-1.0 OR AGPL-3.0-only
# *******************************************************************************

"""Compilation-time symmetry analyzer.

A gate is admitted only if it preserves the active invariant. This is a
rejection rule, not a post-selection filter.

Ising mode (lambda = 0) conserves magnetization and Z-parity. A single X is
forbidden. Spin-flip mode (lambda != 0) conserves prod X, the actual symmetry
of the transverse-field Ising model. A single Z is forbidden; a single X is not.
"""

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum


class SymmetryMode(str, Enum):
    ISING = "ising"
    SPIN_FLIP = "spin_flip"


@dataclass(frozen=True)
class Gate:
    name: str
    qubits: tuple[int, ...]
    kind: str


@dataclass(frozen=True)
class Rejection:
    gate: Gate
    reason: str


class CompilationRejected(Exception):
    """Raised when the analyzer refuses a circuit at compile time."""


class SymmetryAnalyzer:
    def __init__(
        self,
        n: int,
        *,
        conserve_magnetization: bool = False,
        mode: SymmetryMode | str = SymmetryMode.ISING,
    ) -> None:
        if n < 1:
            raise ValueError("n must be positive")
        self.n = n
        self.mode = SymmetryMode(mode)
        self.conserve_magnetization = conserve_magnetization or self.mode is SymmetryMode.ISING

    def check(self, gates: list[Gate]) -> list[Rejection]:
        rejected: list[Rejection] = []
        for gate in gates:
            reason = self._reject_reason(gate)
            if reason is not None:
                rejected.append(Rejection(gate, reason))
        return rejected

    def admit(self, gates: list[Gate]) -> list[Gate]:
        rejected = self.check(gates)
        if rejected:
            detail = "; ".join(f"{r.gate.name}: {r.reason}" for r in rejected)
            raise CompilationRejected(detail)
        return list(gates)

    def _reject_reason(self, gate: Gate) -> str | None:
        for q in gate.qubits:
            if not 0 <= q < self.n:
                return f"qubit {q} outside lattice of size {self.n}"
        if self.mode is SymmetryMode.ISING:
            if gate.kind in {"illegal", "transverse"}:
                return "odd X/Y support changes Z-parity; forbidden sector"
            if gate.kind in {"parity_even", "magnetization", "phase"}:
                return None
        else:
            if gate.kind == "phase":
                return "lone Z anticommutes with prod X; forbidden spin-flip sector"
            if gate.kind in {"illegal", "transverse", "parity_even", "magnetization"}:
                return None
        return f"unknown gate kind {gate.kind}"


def x(q: int) -> Gate:
    return Gate(f"X[{q}]", (q,), "illegal")


def z(q: int) -> Gate:
    return Gate(f"Z[{q}]", (q,), "phase")


def zz(q: int, r: int) -> Gate:
    return Gate(f"ZZ[{q},{r}]", (q, r), "magnetization")


def rx_pair(q: int, r: int) -> Gate:
    """Two transverse flips. Parity-even under prod Z, magnetization-changing."""
    return Gate(f"RX[{q}]*RX[{r}]", (q, r), "parity_even")


def ising_ring(n: int) -> list[Gate]:
    return [zz(i, (i + 1) % n) for i in range(n)]


def transverse_field(n: int) -> list[Gate]:
    return [x(i) for i in range(n)]