qaiji.core.circuit 源代码

# Copyright 2026 Qaiji-IR core developer: Ye Jun <yjmaxpayne@hotmail.com>
# SPDX-License-Identifier: Apache-2.0
"""不可变的门值对象,以及电路级的量子容器。"""

from __future__ import annotations

import math
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING

from qaiji.constants import DEFAULT_TOLERANCE

if TYPE_CHECKING:
    from qaiji.core.classical import ClassicalRegister, Conditional, Measure

__all__ = [
    "CANONICAL_ALIASES",
    "PARAM_REQUIREMENTS",
    "SINGLE_QUBIT_GATES",
    "TWO_QUBIT_GATES",
    "Circuit",
    "Gate",
    "GateType",
]


[文档] class GateType(Enum): """电路级 IR 能够表示的门类型。""" I = "I" # noqa: E741 - 恒等门的标准记法 X = "X" Y = "Y" Z = "Z" H = "H" S = "S" T = "T" RX = "RX" RY = "RY" RZ = "RZ" U3 = "U3" CNOT = "CNOT" CX = "CX" CZ = "CZ" SWAP = "SWAP" RX90 = "RX90" RX180 = "RX180" ISWAP = "ISWAP" SQISWAP = "SQISWAP"
SINGLE_QUBIT_GATES: frozenset[GateType] = frozenset( { GateType.I, GateType.X, GateType.Y, GateType.Z, GateType.H, GateType.S, GateType.T, GateType.RX, GateType.RY, GateType.RZ, GateType.U3, GateType.RX90, GateType.RX180, } ) """恰好作用在一个量子比特上的门类型。""" TWO_QUBIT_GATES: frozenset[GateType] = frozenset( { GateType.CNOT, GateType.CX, GateType.CZ, GateType.SWAP, GateType.ISWAP, GateType.SQISWAP, } ) """恰好作用在两个不同量子比特上的门类型。""" PARAM_REQUIREMENTS: dict[GateType, tuple[int, int]] = { GateType.I: (0, 0), GateType.X: (0, 0), GateType.Y: (0, 0), GateType.Z: (0, 0), GateType.H: (0, 0), GateType.S: (0, 0), GateType.T: (0, 0), GateType.RX: (1, 1), GateType.RY: (1, 1), GateType.RZ: (1, 1), GateType.U3: (3, 3), GateType.CNOT: (0, 0), GateType.CX: (0, 0), GateType.CZ: (0, 0), GateType.SWAP: (0, 0), GateType.RX90: (1, 1), GateType.RX180: (1, 1), GateType.ISWAP: (0, 0), GateType.SQISWAP: (0, 0), } """每种门类型的参数个数上下界(闭区间)。""" CANONICAL_ALIASES: dict[GateType, GateType] = { GateType.CNOT: GateType.CX, } """结构上不同、但共享同一规范形式的门写法。""" _ROTATION_GATES = frozenset({GateType.RX, GateType.RY, GateType.RZ}) def _tau_multiple_exponent(theta: float, *, atol: float = DEFAULT_TOLERANCE) -> int | None: """当 theta 为 2*pi*k 时返回绕数 k,否则返回 None。 这是"theta == 0 (mod 2*pi)"这一判断的唯一真相来源:门折叠、注册表中 RZ 的特例、 保持性相位规则三者都在问同一个问题,绝不能给出不同的答案。用 ``k * tau`` 反推 残差可以让判据在任意量级下都保持精确,而取模的做法会随 theta 增大不断累积表示 误差。 Args: theta: 以弧度表示的角度,保持书写时的原值(未预先归一化)。 atol: 残差的绝对容差。 Returns: 绕数 k;theta 不是整圈时返回 None。注意 k == 0 是合法结果:调用方必须用 ``is not None`` 来判断。 """ winding = round(theta / math.tau) return winding if abs(theta - winding * math.tau) <= atol else None
[文档] @dataclass(frozen=True) class Gate: """作用在一个或多个量子比特上的不可变门。""" gate_type: GateType qubits: tuple[int, ...] params: tuple[float, ...] = () def __post_init__(self) -> None: """归一化序列,并拒绝非法的门状态。""" object.__setattr__(self, "qubits", tuple(self.qubits)) object.__setattr__(self, "params", tuple(self.params)) if not self.qubits: raise ValueError("Gate must act on at least one qubit") if self.gate_type in SINGLE_QUBIT_GATES and len(self.qubits) != 1: raise ValueError(f"Single-qubit gate {self.gate_type.value} requires one qubit") if self.gate_type in TWO_QUBIT_GATES and len(self.qubits) != 2: raise ValueError(f"Two-qubit gate {self.gate_type.value} requires two qubits") minimum, maximum = PARAM_REQUIREMENTS[self.gate_type] if not minimum <= len(self.params) <= maximum: raise ValueError( f"Gate {self.gate_type.value} requires {minimum}-{maximum} parameters, " f"got {len(self.params)}" ) if any(not math.isfinite(param) for param in self.params): raise ValueError("Gate parameters must be finite") if len(self.qubits) != len(set(self.qubits)): raise ValueError("Gate qubits must be unique")
[文档] def canonicalize(self) -> Gate: """返回规范形式的门,且不改变结构相等性。""" gate_type = CANONICAL_ALIASES.get(self.gate_type, self.gate_type) params = self.params if gate_type in _ROTATION_GATES: # 折叠判据读取的是书写时的原始角度:先归一化会让大量级下的整圈被取模 # 残差掩盖。归一化只施加于存活下来的那个旋转门。 if _tau_multiple_exponent(params[0]) is not None: return Gate(GateType.I, self.qubits) params = (params[0] % math.tau, *params[1:]) return Gate(gate_type, self.qubits, params)
def _node_eq( left: Gate | Measure | Conditional, right: Gate | Measure | Conditional, ) -> bool: """比较操作节点,并在所有门体内部一致地施加容差。""" from qaiji.core.classical import Conditional, Measure if isinstance(left, Gate) and isinstance(right, Gate): return ( left.gate_type is right.gate_type and left.qubits == right.qubits and len(left.params) == len(right.params) and all( abs(left_param - right_param) <= DEFAULT_TOLERANCE for left_param, right_param in zip(left.params, right.params, strict=True) ) ) if isinstance(left, Measure) and isinstance(right, Measure): return left == right if isinstance(left, Conditional) and isinstance(right, Conditional): return ( left.register == right.register and left.value == right.value and len(left.body) == len(right.body) and all( _node_eq(left_gate, right_gate) for left_gate, right_gate in zip(left.body, right.body, strict=True) ) ) return False
[文档] class Circuit: """定宽量子比特之上的有序操作序列。""" __hash__ = None # type: ignore[assignment] # 显式声明为不可哈希的值容器。 def __init__(self, num_qubits: int): if num_qubits < 1: raise ValueError("Circuit must have at least one qubit") self.num_qubits = num_qubits self.gates: list[Gate | Measure | Conditional] = [] self.cregs: list[ClassicalRegister] = [] def _validate_gate_qubits(self, gate: Gate) -> None: for qubit in gate.qubits: if qubit < 0 or qubit >= self.num_qubits: raise ValueError( f"Gate acts on qubit {qubit}, but circuit only has " f"qubits 0-{self.num_qubits - 1}" ) def _has_register(self, register: ClassicalRegister) -> bool: return any(existing is register or existing == register for existing in self.cregs)
[文档] def add_register(self, register: ClassicalRegister) -> None: """追加一个寄存器,同时保证声明名称唯一。""" if any(existing.name == register.name for existing in self.cregs): raise ValueError(f"Classical register {register.name!r} is already declared") self.cregs.append(register)
[文档] def add_gate(self, gate: Gate) -> None: """追加一个作用比特落在电路范围内的门。""" self._validate_gate_qubits(gate) self.gates.append(gate)
[文档] def add_measure(self, measurement: Measure) -> None: """追加一次测量,要求比特在范围内且目标寄存器已声明。""" if measurement.qubit < 0 or measurement.qubit >= self.num_qubits: raise ValueError( f"Measurement uses qubit {measurement.qubit}, but circuit only has " f"qubits 0-{self.num_qubits - 1}" ) if not self._has_register(measurement.target.register): raise ValueError("Measurement target register is not declared by this circuit") self.gates.append(measurement)
[文档] def add_conditional(self, conditional: Conditional) -> None: """追加一个条件节点,其寄存器须已声明、体内门须在范围内。""" if not self._has_register(conditional.register): raise ValueError("Conditional register is not declared by this circuit") for gate in conditional.body: self._validate_gate_qubits(gate) self.gates.append(conditional)
[文档] def h(self, qubit: int) -> None: """追加一个 Hadamard 门。""" self.add_gate(Gate(GateType.H, (qubit,)))
[文档] def x(self, qubit: int) -> None: """追加一个 Pauli-X 门。""" self.add_gate(Gate(GateType.X, (qubit,)))
[文档] def y(self, qubit: int) -> None: """追加一个 Pauli-Y 门。""" self.add_gate(Gate(GateType.Y, (qubit,)))
[文档] def z(self, qubit: int) -> None: """追加一个 Pauli-Z 门。""" self.add_gate(Gate(GateType.Z, (qubit,)))
[文档] def s(self, qubit: int) -> None: """追加一个 S 门。""" self.add_gate(Gate(GateType.S, (qubit,)))
[文档] def t(self, qubit: int) -> None: """追加一个 T 门。""" self.add_gate(Gate(GateType.T, (qubit,)))
[文档] def rx(self, qubit: int, angle: float = math.pi) -> None: """追加一个绕 X 轴的旋转。""" self.add_gate(Gate(GateType.RX, (qubit,), (angle,)))
[文档] def ry(self, qubit: int, angle: float) -> None: """追加一个绕 Y 轴的旋转。""" self.add_gate(Gate(GateType.RY, (qubit,), (angle,)))
[文档] def rz(self, qubit: int, angle: float) -> None: """追加一个绕 Z 轴的旋转。""" self.add_gate(Gate(GateType.RZ, (qubit,), (angle,)))
[文档] def rx90(self, qubit: int, phase: float = 0.0) -> None: """追加一个带相位参数的四分之一圈 X 门。""" self.add_gate(Gate(GateType.RX90, (qubit,), (phase,)))
[文档] def rx180(self, qubit: int, phase: float = 0.0) -> None: """追加一个带相位参数的半圈 X 门。""" self.add_gate(Gate(GateType.RX180, (qubit,), (phase,)))
[文档] def u3(self, qubit: int, theta: float, phi: float, lam: float) -> None: """追加一个通用单比特门。""" self.add_gate(Gate(GateType.U3, (qubit,), (theta, phi, lam)))
[文档] def cnot(self, control: int, target: int) -> None: """追加一个受控非门。""" self.add_gate(Gate(GateType.CNOT, (control, target)))
[文档] def cx(self, control: int, target: int) -> None: """追加一个受控 X 门。""" self.add_gate(Gate(GateType.CX, (control, target)))
[文档] def cz(self, control: int, target: int) -> None: """追加一个受控 Z 门。""" self.add_gate(Gate(GateType.CZ, (control, target)))
[文档] def copy(self) -> Circuit: """复制可变容器,同时安全地共享不可变节点。""" copied = Circuit(self.num_qubits) copied.gates = list(self.gates) copied.cregs = list(self.cregs) return copied
[文档] def canonicalize(self) -> Circuit: """返回一个新电路,其中每个门都被约化为规范形式。 测量原样透传;每个 Conditional 都会用规范化后的体内门重建。``qaiji.codec`` 不消费本方法 —— OpenQASM 往返始终是结构性的,绝不做隐式归一化。 Returns: 一个新电路,其宽度、寄存器声明、节点数量与节点种类序列都与当前电路 逐位置一致。 """ from qaiji.core.classical import Conditional, Measure canonical = Circuit(self.num_qubits) for register in self.cregs: canonical.add_register(register) for operation in self.gates: if isinstance(operation, Gate): canonical.add_gate(operation.canonicalize()) elif isinstance(operation, Measure): canonical.add_measure(operation) else: body = tuple(gate.canonicalize() for gate in operation.body) canonical.add_conditional(Conditional(operation.register, operation.value, body)) return canonical
def __eq__(self, other: object) -> bool: """比较电路结构,其中门的数值参数按容差比较。""" return ( isinstance(other, Circuit) and self.num_qubits == other.num_qubits and self.cregs == other.cregs and len(self.gates) == len(other.gates) and all( _node_eq(left, right) for left, right in zip(self.gates, other.gates, strict=True) ) ) def __len__(self) -> int: """返回电路中的操作数量。""" return len(self.gates) def __str__(self) -> str: """渲染一份紧凑的、人类可读的电路清单。""" lines = [f"Circuit({self.num_qubits} qubits)"] for index, operation in enumerate(self.gates): if isinstance(operation, Gate): params = "" if operation.params: params = f"({', '.join(f'{param:.4f}' for param in operation.params)})" qubits = ", ".join(str(qubit) for qubit in operation.qubits) lines.append( f" {index:2d}: {operation.gate_type.value}{params} -> qubits [{qubits}]" ) else: lines.append(f" {index:2d}: {operation!r}") return "\n".join(lines)