Download sim/p3q_tensor_sim.py from Snapkitty/p3q-tsql: direct link, hf CLI and curl.
- Browser
- Download file 10.6 kB
-
https://huggingface.co/Snapkitty/p3q-tsql/resolve/main/sim/p3q_tensor_sim.py
- Command line
-
hf download hf://Snapkitty/p3q-tsql/sim/p3q_tensor_sim.py
-
curl -L -o p3q_tensor_sim.py https://huggingface.co/Snapkitty/p3q-tsql/resolve/main/sim/p3q_tensor_sim.py
10.6 kB
| """ | |
| P3Q Tensor Network Simulator | |
| MPS (Matrix Product State) backend for Grover circuit simulation. | |
| Authors: Ahmad Ali Parr, Jessica L. Williams (SNAPKITTYWEST) | |
| Scope / limitations | |
| -------------------- | |
| Classical simulation only. Not a quantum computer. | |
| Exact MPS simulation: ~50 qubits. | |
| Truncated (bond-dimension limited): ~200 qubits with accuracy loss. | |
| 4-round AES Grover (1200 qubits): classical simulation is infeasible. | |
| This module provides a research framework for small sub-circuits. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| from typing import List, Tuple, Dict, Optional | |
| from dataclasses import dataclass | |
| # ββ Tensor Primitive βββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class Tensor: | |
| """Named-index tensor for contraction tracking.""" | |
| data: np.ndarray | |
| indices: List[str] | |
| def shape(self) -> Tuple[int, ...]: | |
| return self.data.shape | |
| def contract(self, other: Tensor, pairs: List[Tuple[str, str]]) -> Tensor: | |
| """Einsum contraction over named index pairs.""" | |
| contracted = {a for a, b in pairs} | {b for a, b in pairs} | |
| free_self = [i for i in self.indices if i not in contracted] | |
| free_other = [i for i in other.indices if i not in contracted] | |
| label_map: Dict[str, str] = {} | |
| c = ord('a') | |
| for idx in free_self + free_other + list(contracted): | |
| if idx not in label_map: | |
| label_map[idx] = chr(c); c += 1 | |
| s = ''.join(label_map[i] for i in self.indices) | |
| o = ''.join(label_map[i] for i in other.indices) | |
| r = ''.join(label_map[i] for i in free_self + free_other) | |
| data = np.einsum(f"{s},{o}->{r}", self.data, other.data, optimize='greedy') | |
| return Tensor(data, free_self + free_other) | |
| def svd_truncate( | |
| self, | |
| left_idx: List[str], | |
| right_idx: List[str], | |
| max_bond: int, | |
| eps: float = 1e-12, | |
| ) -> Tuple[Tensor, Tensor]: | |
| """SVD truncation for MPS bond compression.""" | |
| left_shape = [self.data.shape[self.indices.index(i)] for i in left_idx] | |
| right_shape = [self.data.shape[self.indices.index(i)] for i in right_idx] | |
| mat = self.data.reshape((int(np.prod(left_shape)), int(np.prod(right_shape)))) | |
| U, S, Vh = np.linalg.svd(mat, full_matrices=False) | |
| keep = min(max_bond, int(np.sum(S > eps))) | |
| U, S, Vh = U[:, :keep], S[:keep], Vh[:keep, :] | |
| sq = np.sqrt(S) | |
| left = Tensor((U * sq).reshape(left_shape + [keep]), left_idx + ['bond']) | |
| right = Tensor((sq[:, None] * Vh).reshape([keep] + right_shape), ['bond'] + right_idx) | |
| return left, right | |
| # ββ Gate Library βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class GateTensor: | |
| H = Tensor(np.array([[1,1],[1,-1]], dtype=complex)/np.sqrt(2), ['in','out']) | |
| X = Tensor(np.array([[0,1],[1,0]], dtype=complex), ['in','out']) | |
| Z = Tensor(np.array([[1,0],[0,-1]], dtype=complex), ['in','out']) | |
| T = Tensor(np.diag([1, np.exp(1j*np.pi/4)]).astype(complex), ['in','out']) | |
| Tdg = Tensor(np.diag([1, np.exp(-1j*np.pi/4)]).astype(complex), ['in','out']) | |
| S = Tensor(np.diag([1, 1j]).astype(complex), ['in','out']) | |
| def CX() -> Tensor: | |
| d = np.zeros((2,2,2,2), dtype=complex) | |
| d[0,0,0,0]=d[0,1,0,1]=d[1,0,1,1]=d[1,1,1,0]=1 | |
| return Tensor(d, ['c_in','t_in','c_out','t_out']) | |
| def CZ() -> Tensor: | |
| d = np.zeros((2,2,2,2), dtype=complex) | |
| d[0,0,0,0]=d[0,1,0,1]=d[1,0,1,0]=1; d[1,1,1,1]=-1 | |
| return Tensor(d, ['c_in','t_in','c_out','t_out']) | |
| # ββ MPS State ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class MPSState: | |
| """Matrix Product State for n qubits.""" | |
| def __init__(self, n: int, max_bond: int = 256): | |
| self.n = n | |
| self.max_bond = max_bond | |
| # Initialize |0>^βn as rank-1 MPS | |
| self._sv = [np.array([1.0, 0.0], dtype=complex) for _ in range(n)] | |
| def apply_single(self, q: int, gate: np.ndarray) -> None: | |
| """Apply 2Γ2 gate matrix to qubit q.""" | |
| self._sv[q] = gate @ self._sv[q] | |
| def prob_zero(self, q: int) -> float: | |
| """Probability of measuring |0> on qubit q.""" | |
| return float(np.abs(self._sv[q][0])**2 / np.dot(self._sv[q].conj(), self._sv[q]).real) | |
| def measure(self, q: int) -> Tuple[int, float]: | |
| """Collapse qubit q to a classical bit.""" | |
| p0 = self.prob_zero(q) | |
| outcome = 0 if np.random.random() < p0 else 1 | |
| new = np.zeros(2, dtype=complex); new[outcome] = 1.0 | |
| self._sv[q] = new | |
| return outcome, p0 if outcome == 0 else 1.0 - p0 | |
| # ββ AES Tensor Blocks ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class AESTensorBlocks: | |
| """Concrete tensor representations for AES GF(2^8) operations.""" | |
| def xtime_gate_tensor() -> Tensor: | |
| """ | |
| 8Γ8 binary linear transformation for GF(2^8) xtime over 8 qubits. | |
| data[in_bits, out_bits] = 1 for each valid xtime mapping. | |
| """ | |
| data = np.zeros((2,)*16, dtype=complex) | |
| for val in range(256): | |
| msb = (val >> 7) & 1 | |
| shifted = (val << 1) & 0xFF | |
| result = (shifted ^ 0x1B) if msb else shifted | |
| in_bits = tuple((val >> (7-i)) & 1 for i in range(8)) | |
| out_bits = tuple((result >> (7-i)) & 1 for i in range(8)) | |
| data[in_bits + out_bits] = 1.0 | |
| in_idx = [f'x_in_{i}' for i in range(8)] | |
| out_idx = [f'x_out_{i}' for i in range(8)] | |
| return Tensor(data, in_idx + out_idx) | |
| def verify_xtime_tensor() -> bool: | |
| """Verify xtime tensor against the Pascal implementation.""" | |
| t = AESTensorBlocks.xtime_gate_tensor() | |
| # Test canonical values | |
| def xtime_ref(b): | |
| s = (b << 1) & 0xFF | |
| return s ^ 0x1B if b & 0x80 else s | |
| for val in [0x00, 0x01, 0x40, 0x80, 0xFF, 0xD4]: | |
| expected = xtime_ref(val) | |
| in_bits = tuple((val >> (7-i)) & 1 for i in range(8)) | |
| # Sum over output dimension should give expected output bits | |
| # Sum over last 8 output dimensions to get probability per output bit | |
| out_data = t.data[in_bits] # shape (2,)*8 | |
| actual_int = 0 | |
| for bit_pos in range(8): | |
| # marginalise all except this output bit | |
| axes = tuple(j for j in range(8) if j != bit_pos) | |
| prob1 = float(np.sum(np.abs(out_data), axis=axes)[1].real) | |
| if prob1 > 0.5: | |
| actual_int |= (1 << (7 - bit_pos)) | |
| if actual_int != expected: | |
| return False | |
| return True | |
| # ββ Grover Tensor Network ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class GroverTensorNetwork: | |
| """ | |
| Tensor network representation of a Grover iteration. | |
| Limited to small qubit counts for classical simulation. | |
| Full AES-4 Grover (1200 qubits) is NOT simulable classically. | |
| """ | |
| def __init__( | |
| self, | |
| n_key: int = 8, | |
| n_state: int = 8, | |
| n_anc: int = 16, | |
| ): | |
| self.n_key = n_key | |
| self.n_state = n_state | |
| self.n_anc = n_anc | |
| self.total = n_key + n_state + n_anc | |
| self.mps = MPSState(self.total, max_bond=64) | |
| def hadamard_layer(self) -> None: | |
| H = np.array([[1,1],[1,-1]], dtype=complex) / np.sqrt(2) | |
| for i in range(self.n_key): | |
| self.mps.apply_single(i, H) | |
| def toy_oracle(self, marked: int) -> None: | |
| """Phase oracle marking one key state (toy, for testing).""" | |
| Z = np.array([[1,0],[0,-1]], dtype=complex) | |
| X = np.array([[0,1],[1,0]], dtype=complex) | |
| for bit in range(self.n_key): | |
| if not ((marked >> (self.n_key - 1 - bit)) & 1): | |
| self.mps.apply_single(bit, X) | |
| # Apply Z to last qubit (simulate multi-controlled Z) | |
| self.mps.apply_single(self.n_key - 1, Z) | |
| for bit in range(self.n_key): | |
| if not ((marked >> (self.n_key - 1 - bit)) & 1): | |
| self.mps.apply_single(bit, X) | |
| def diffusion(self) -> None: | |
| H = np.array([[1,1],[1,-1]], dtype=complex) / np.sqrt(2) | |
| X = np.array([[0,1],[1,0]], dtype=complex) | |
| Z = np.array([[1,0],[0,-1]], dtype=complex) | |
| for i in range(self.n_key): self.mps.apply_single(i, H) | |
| for i in range(self.n_key): self.mps.apply_single(i, X) | |
| self.mps.apply_single(self.n_key - 1, Z) | |
| for i in range(self.n_key): self.mps.apply_single(i, X) | |
| for i in range(self.n_key): self.mps.apply_single(i, H) | |
| def run(self, marked: int, iterations: int, shots: int = 1000) -> Dict[int, int]: | |
| self.hadamard_layer() | |
| for _ in range(iterations): | |
| self.toy_oracle(marked) | |
| self.diffusion() | |
| counts: Dict[int, int] = {} | |
| for _ in range(shots): | |
| val = 0 | |
| for i in range(self.n_key): | |
| bit, _ = self.mps.measure(i) | |
| val = (val << 1) | bit | |
| counts[val] = counts.get(val, 0) + 1 | |
| return counts | |
| # ββ Demo βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def demo(): | |
| print("=== xtime tensor verification ===") | |
| ok = AESTensorBlocks.verify_xtime_tensor() | |
| print(f"xtime_gate_tensor: {'PASS' if ok else 'FAIL'}") | |
| print("\n=== Toy Grover (n=8, marked=42, iterations=8) ===") | |
| n = 8 | |
| marked = 42 | |
| iters = int(np.pi / 4 * np.sqrt(2**n)) | |
| gtn = GroverTensorNetwork(n_key=n, n_state=0, n_anc=0) | |
| counts = gtn.run(marked, iters, shots=2000) | |
| top = sorted(counts.items(), key=lambda x: -x[1])[:5] | |
| for state, count in top: | |
| flag = " β MARKED" if state == marked else "" | |
| print(f" |{state:0{n}b}> ({state:3d}): {count/20:.1f}%{flag}") | |
| if __name__ == "__main__": | |
| demo() | |