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)]
|