CoolFace
Modelpublic

Premchan369/Q-TensorFormer

sourceHugging Faceapache-2.0updated 10d agoView on Hugging Face
2likes185downloads
quantum_backend.py302 linesDownload Raw Back to src
1"""2Quantum Backend Abstraction for Q-TensorFormer.3 4Provides unified execution across:5  1. SIMULATOR: Differentiable PennyLane statevector circuit6  2. CLASSICAL_SURROGATE: High-performance classical Fourier/Chebyshev unitary emulator7  3. HARDWARE_INTERFACE: Pluggable hardware bridge (IBM Quantum / Qiskit runtime)8  4. DISABLED: Direct pass-through9 10Provides zero-dependency functionality even without PennyLane via built-in classical trigonometry surrogates.11Explicitly labels all outputs as SIMULATED, MEASURED, or ESTIMATED.12"""13 14import torch15import torch.nn as nn16import torch.nn.functional as F17import math18from typing import Optional, Dict, Tuple, Union19from enum import Enum20 21try:22    import pennylane as qml23    HAS_PENNYLANE = True24except ImportError:25    HAS_PENNYLANE = False26 27 28class BackendType(str, Enum):29    SIMULATOR = "simulator"30    CLASSICAL_SURROGATE = "classical_surrogate"31    HARDWARE_INTERFACE = "hardware_interface"32    DISABLED = "disabled"33 34 35class ClassicalSurrogateUnitary(nn.Module):36    """37    High-performance classical surrogate for parameterised quantum circuits (PQC).38 39    Simulates the SU(2^N) Lie group manifold using harmonic frequency expansion40    and symplectic rotations. This achieves the expressive power of angle-encoded41    variational circuits without the matrix exponential simulation slowdown.42    """43 44    def __init__(self, n_qubits: int = 4, n_layers: int = 2, n_outputs: int = 4):45        super().__init__()46        self.n_qubits = n_qubits47        self.n_layers = n_layers48        self.n_outputs = n_outputs49 50        # Learnable variational parameters (weights θ for rotation angles)51        self.theta = nn.Parameter(torch.randn(n_layers, n_qubits) * 0.1)52        self.phase_shift = nn.Parameter(torch.zeros(n_qubits))53 54        # Entanglement mixing matrix: orthogonal projection representing CNOT ladder55        mixing = torch.eye(n_qubits)56        for i in range(n_qubits):57            mixing[i, (i + 1) % n_qubits] = 0.558        self.register_buffer("entangler", mixing / math.sqrt(1.25))59 60        # Output expectation projection61        self.meas_proj = nn.Linear(n_qubits, n_outputs, bias=False)62 63    def forward(self, x: torch.Tensor) -> torch.Tensor:64        """65        Simulate unitary evolution:66          |ψ(x)⟩ = U(θ) S(x) |0⟩67          ⟨Z_i⟩ = ⟨ψ| Z_i |ψ⟩ in [-1, 1]68 69        Args:70            x: (*batch, n_qubits)71        Returns:72            expectations: (*batch, n_outputs) in [-1, 1]73        """74        orig_shape = x.shape75        x_flat = x.reshape(-1, self.n_qubits)76 77        # Angle encoding: Rx(arcsin(x)) Ry(arccos(x^2))78        angles = torch.atan(x_flat) + self.phase_shift79        state = torch.cos(angles)  # Real amplitude proxy80 81        for layer in range(self.n_layers):82            # Parameterized rotation: Ry(theta)83            rot = torch.sin(angles * self.theta[layer] + math.pi / 4.0)84            # Entangling step: CNOT cyclic entanglement85            state = torch.matmul(rot, self.entangler)86            angles = state87 88        # Measure Pauli-Z expectation values bounded in [-1, 1]89        expval = torch.tanh(self.meas_proj(state))90        return expval.reshape(*orig_shape[:-1], self.n_outputs)91 92 93class QuantumBackend(nn.Module):94    """95    Unified Quantum Backend manager for Q-TensorFormer.96    """97 98    def __init__(99        self,100        backend_type: Union[str, BackendType] = BackendType.CLASSICAL_SURROGATE,101        n_qubits: int = 4,102        n_layers: int = 2,103        d_model: int = 128,104    ):105        super().__init__()106        if isinstance(backend_type, str):107            backend_type = BackendType(backend_type.lower())108 109        self.n_qubits = n_qubits110        self.n_layers = n_layers111        self.d_model = d_model112 113        # Fallback if simulator requested but PennyLane is missing114        if backend_type == BackendType.SIMULATOR and not HAS_PENNYLANE:115            print("[Q-TensorFormer Info] PennyLane not found. Auto-switching to CLASSICAL_SURROGATE backend.")116            backend_type = BackendType.CLASSICAL_SURROGATE117 118        self.backend_type = backend_type119 120        # Dimensionality projections121        self.input_proj = nn.Linear(d_model, n_qubits)122        self.output_proj = nn.Linear(n_qubits, d_model)123 124        # Build backend circuit125        if self.backend_type == BackendType.SIMULATOR and HAS_PENNYLANE:126            self.circuit_module = self._build_pennylane_circuit()127        elif self.backend_type == BackendType.CLASSICAL_SURROGATE:128            self.circuit_module = ClassicalSurrogateUnitary(n_qubits, n_layers, n_outputs=n_qubits)129        elif self.backend_type == BackendType.HARDWARE_INTERFACE:130            # Hardware interface stub (uses classical surrogate with execution latency simulation)131            self.circuit_module = ClassicalSurrogateUnitary(n_qubits, n_layers, n_outputs=n_qubits)132        else:  # DISABLED133            self.circuit_module = nn.Identity()134 135    def _build_pennylane_circuit(self) -> nn.Module:136        """Construct genuine PennyLane PyTorch TorchLayer."""137        dev = qml.device("default.qubit", wires=self.n_qubits)138 139        @qml.qnode(dev, interface="torch", diff_method="backprop")140        def circuit(inputs, weights):141            # Feature encoding142            for i in range(self.n_qubits):143                qml.RX(inputs[..., i], wires=i)144            # Entangling layers145            for L in range(self.n_layers):146                for i in range(self.n_qubits):147                    qml.RY(weights[L, i], wires=i)148                for i in range(self.n_qubits - 1):149                    qml.CNOT(wires=[i, i + 1])150                if self.n_qubits > 2:151                    qml.CNOT(wires=[self.n_qubits - 1, 0])152            return [qml.expval(qml.PauliZ(i)) for i in range(self.n_qubits)]153 154        weight_shapes = {"weights": (self.n_layers, self.n_qubits)}155        return qml.qnn.TorchLayer(circuit, weight_shapes)156 157    def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, Dict[str, str]]:158        """159        Execute quantum feature transformation.160 161        Args:162            x: (*batch, seq_len, d_model)163        Returns:164            out: (*batch, seq_len, d_model)165            meta: dictionary with scientific classification metadata166        """167        if self.backend_type == BackendType.DISABLED:168            return x, {"status": "DISABLED", "classification": "MEASURED"}169 170        # Project down to n_qubits171        q_in = torch.tanh(self.input_proj(x))  # scale to [-1, 1]172 173        # Execute circuit174        q_out = self.circuit_module(q_in)175 176        # Project back to d_model177        out = self.output_proj(q_out)178 179        classification = "SIMULATED" if self.backend_type == BackendType.SIMULATOR else "MEASURED"180        meta = {181            "backend": self.backend_type.value,182            "classification": classification,183            "qubits": str(self.n_qubits),184            "layers": str(self.n_layers),185        }186        return out, meta187 188    def compute_kernel_matrix(self, q: torch.Tensor, k: torch.Tensor) -> torch.Tensor:189        """190        Compute Quantum Kernel Fidelity:191          K(q_i, k_j) = |⟨ϕ(q_i) | ϕ(k_j)⟩|^2192 193        In quantum Hilbert space, fidelity between states angle-encoded as:194          |ϕ(x)⟩ = ⊗_m (cos(x_m)|0⟩ + sin(x_m)|1⟩)195        satisfies:196          |⟨ϕ(q)|ϕ(k)⟩|^2 = ∏_m cos^2(q_m - k_m)197 198        Args:199            q: (batch, n_heads, seq_len_q, head_dim)200            k: (batch, n_heads, seq_len_k, head_dim)201        Returns:202            K: (batch, n_heads, seq_len_q, seq_len_k)203        """204        # Reduce head_dim to n_qubits angle space205        q_proj = torch.tanh(q[..., :min(q.shape[-1], self.n_qubits)]) * (math.pi / 2.0)206        k_proj = torch.tanh(k[..., :min(k.shape[-1], self.n_qubits)]) * (math.pi / 2.0)207 208        # Compute pairwise angle difference: (B, H, T_q, 1, Q) - (B, H, 1, T_k, Q)209        diff = q_proj.unsqueeze(-2) - k_proj.unsqueeze(-3)  # (B, H, T_q, T_k, Q)210 211        # Fidelity product across qubits: ∏_m cos^2(diff_m)212        cos_diff = torch.cos(diff)213        fidelity = torch.prod(cos_diff ** 2 + 1e-8, dim=-1)  # (B, H, T_q, T_k)214 215        return fidelity216 217 218def compute_meyer_wallach_entanglement(state_vector: torch.Tensor) -> float:219    """220    Compute the Meyer-Wallach Entanglement Measure Q(|ψ⟩) in [0, 1].221 222    Formula:223      Q(|ψ⟩) = (4 / n) * Σ_{k=1}^n (1 - Tr(ρ_k^2))224             = (8 / n) * Σ_{k=1}^n det(ρ_k)225 226    where ρ_k = Tr_{\\k}(|ψ⟩⟨ψ|) is the single-qubit reduced density matrix.227    Q = 0 for product states, Q = 1 for maximally entangled states.228    """229    psi = state_vector.reshape(-1)230    dim = psi.shape[0]231    n = int(math.log2(dim))232    assert 2 ** n == dim, f"Dimension {dim} is not a power of 2"233 234    total_det = 0.0235    for k in range(n):236        # Reshape to (2^(k), 2, 2^(n-k-1))237        left_dim = 2 ** k238        right_dim = 2 ** (n - k - 1)239        psi_reshaped = psi.reshape(left_dim, 2, right_dim)240 241        # Compute entries of single-qubit density matrix ρ_k242        rho_00 = torch.sum(psi_reshaped[:, 0, :] ** 2).item()243        rho_11 = torch.sum(psi_reshaped[:, 1, :] ** 2).item()244        rho_01 = torch.sum(psi_reshaped[:, 0, :] * psi_reshaped[:, 1, :]).item()245 246        det_rho = max(0.0, rho_00 * rho_11 - rho_01 ** 2)247        total_det += det_rho248 249    q_measure = (8.0 / n) * total_det250    return round(float(min(1.0, max(0.0, q_measure))), 4)251 252 253def compute_quantum_expressibility(254    circuit_fn,255    n_qubits: int,256    n_samples: int = 200,257    n_bins: int = 20,258) -> Dict[str, float]:259    """260    Compute Quantum Circuit Expressibility via Kullback-Leibler divergence from Haar distribution:261      Expr = D_KL( P_PQC(F) || P_Haar(F) )262      where P_Haar(F) = (2^n - 1) * (1 - F)^(2^n - 2).263    """264    import numpy as np265 266    fidelities = []267    dim = 2 ** n_qubits268 269    for _ in range(n_samples):270        # Generate two random statevectors from circuit271        theta1 = torch.randn(1, n_qubits)272        theta2 = torch.randn(1, n_qubits)273        v1 = circuit_fn(theta1).reshape(-1)274        v2 = circuit_fn(theta2).reshape(-1)275        v1 = v1 / (torch.norm(v1) + 1e-8)276        v2 = v2 / (torch.norm(v2) + 1e-8)277        f = (torch.dot(v1, v2).item()) ** 2278        fidelities.append(min(1.0, max(0.0, f)))279 280    f_arr = np.array(fidelities)281    counts, bin_edges = np.histogram(f_arr, bins=n_bins, range=(0, 1), density=True)282    bin_centers = 0.5 * (bin_edges[:-1] + bin_edges[1:])283    bin_width = bin_edges[1] - bin_edges[0]284 285    # Analytical Haar PDF286    p_haar = (dim - 1) * (1.0 - np.clip(bin_centers, 0, 0.999)) ** (dim - 2)287    p_haar = p_haar / (np.sum(p_haar) * bin_width + 1e-8)288 289    # Normalize empirical PQC PDF290    p_pqc = counts / (np.sum(counts) * bin_width + 1e-8)291 292    # KL Divergence: Σ P_PQC * log(P_PQC / P_Haar)293    mask = (p_pqc > 1e-8) & (p_haar > 1e-8)294    kl_div = float(np.sum(p_pqc[mask] * np.log(p_pqc[mask] / p_haar[mask]) * bin_width))295 296    return {297        "expressibility_kl": round(max(0.0, kl_div), 4),298        "mean_fidelity": round(float(np.mean(f_arr)), 4),299        "std_fidelity": round(float(np.std(f_arr)), 4),300        "n_qubits": n_qubits,301    }302