| """ |
| 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 |
|
|
|
|
| |
|
|
| @dataclass |
| 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 |
|
|
|
|
| |
|
|
| 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']) |
|
|
| @staticmethod |
| 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']) |
|
|
| @staticmethod |
| 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']) |
|
|
|
|
| |
|
|
| 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 |
| |
| 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 |
|
|
|
|
| |
|
|
| class AESTensorBlocks: |
| """Concrete tensor representations for AES GF(2^8) operations.""" |
|
|
| @staticmethod |
| 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) |
|
|
| @staticmethod |
| def verify_xtime_tensor() -> bool: |
| """Verify xtime tensor against the Pascal implementation.""" |
| t = AESTensorBlocks.xtime_gate_tensor() |
| |
| 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)) |
| |
| |
| out_data = t.data[in_bits] |
| actual_int = 0 |
| for bit_pos in range(8): |
| |
| 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 |
|
|
|
|
| |
|
|
| 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) |
| |
| 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 |
|
|
|
|
| |
|
|
| 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() |
|
|