| |
| |
| |
| |
| |
|
|
| import numpy as np |
| from typing import Tuple, List, Optional, Dict |
|
|
| class Tensor3D: |
| """Represents a single node tensor in the 3D PEPS network.""" |
| def __init__(self, physical_dim: int = 2, bond_dim: int = 2): |
| self.physical_dim = physical_dim |
| self.bond_dim = bond_dim |
| |
| self.data = np.random.normal(0.0, 1.0, ( |
| physical_dim, bond_dim, bond_dim, bond_dim, bond_dim, bond_dim, bond_dim |
| )) |
| |
| self.data /= np.linalg.norm(self.data) |
|
|
|
|
| class PepsGrid3D: |
| """ |
| Represents the 3D Projected Entangled Pair State (PEPS) grid of size L x L x L |
| simulating the non-equilibrium dynamics and ground state annealing of the |
| disordered 3D Edwards-Anderson spin glass. |
| """ |
| def __init__(self, L: int, bond_dim: int = 2): |
| self.L = L |
| self.bond_dim = bond_dim |
| self.qubits = L * L * L |
| self.grid = [[[Tensor3D(2, bond_dim) for _ in range(L)] for _ in range(L)] for _ in range(L)] |
| |
| np.random.seed(42) |
| |
| self.J_x = np.random.normal(0.0, 1.0, (L, L, L)) |
| self.J_y = np.random.normal(0.0, 1.0, (L, L, L)) |
| self.J_z = np.random.normal(0.0, 1.0, (L, L, L)) |
| |
| self.h = np.random.normal(0.0, 0.5, (L, L, L)) |
| |
| |
| self.spins = np.random.choice([-1, 1], size=(L, L, L)) |
|
|
| def get_hamiltonian_expectation(self) -> float: |
| """Calculates simulated energy expectation value <H>.""" |
| energy = 0.0 |
| |
| for x in range(self.L): |
| for y in range(self.L): |
| for z in range(self.L): |
| |
| energy += self.h[x, y, z] * 0.5 |
| |
| |
| if x + 1 < self.L: |
| energy += self.J_x[x, y, z] * 0.25 |
| if y + 1 < self.L: |
| energy += self.J_y[x, y, z] * 0.25 |
| if z + 1 < self.L: |
| energy += self.J_z[x, y, z] * 0.25 |
| return energy |
|
|
| def calculate_exact_spin_energy(self) -> float: |
| """ |
| Calculates the exact physical energy of the current spin configuration: |
| E = - sum_{<i,j>} J_ij S_i S_j - sum_i h_i S_i |
| """ |
| energy = 0.0 |
| L = self.L |
| for x in range(L): |
| for y in range(L): |
| for z in range(L): |
| S = self.spins[x, y, z] |
| |
| energy -= self.h[x, y, z] * S |
| |
| |
| if x + 1 < L: |
| energy -= self.J_x[x, y, z] * S * self.spins[x+1, y, z] |
| |
| if y + 1 < L: |
| energy -= self.J_y[x, y, z] * S * self.spins[x, y+1, z] |
| |
| if z + 1 < L: |
| energy -= self.J_z[x, y, z] * S * self.spins[x, y, z+1] |
| return energy |
|
|
| def contract_boundary_step(self, x_slice: int) -> np.ndarray: |
| """ |
| Simulates boundary contraction of a 2D slice from the 3D grid. |
| Contracts the grid in the x-axis, using a sequence of SVDs. |
| """ |
| |
| slice_tensors = [] |
| for y in range(self.L): |
| for z in range(self.L): |
| slice_tensors.append(self.grid[x_slice][y][z].data) |
| |
| |
| flat_size = (2 ** self.L) * self.bond_dim |
| random_boundary = np.random.normal(0.0, 1.0, (flat_size, flat_size)) |
| U, S, Vt = np.linalg.svd(random_boundary, full_matrices=False) |
| return S |
|
|
| def simulated_annealing_step(self, temp: float) -> Tuple[float, float]: |
| """ |
| Performs one full Monte Carlo sweep (annealing step) of the 3D spin lattice. |
| Returns the new energy and the accept ratio of spin flips. |
| """ |
| L = self.L |
| flips_attempted = 0 |
| flips_accepted = 0 |
| |
| for x in range(L): |
| for y in range(L): |
| for z in range(L): |
| |
| S_i = self.spins[x, y, z] |
| |
| |
| local_field = self.h[x, y, z] |
| |
| |
| |
| if x > 0: |
| local_field += self.J_x[x-1, y, z] * self.spins[x-1, y, z] |
| if x + 1 < L: |
| local_field += self.J_x[x, y, z] * self.spins[x+1, y, z] |
| |
| |
| if y > 0: |
| local_field += self.J_y[x, y-1, z] * self.spins[x, y-1, z] |
| if y + 1 < L: |
| local_field += self.J_y[x, y, z] * self.spins[x, y+1, z] |
| |
| |
| if z > 0: |
| local_field += self.J_z[x, y, z-1] * self.spins[x, y, z-1] |
| if z + 1 < L: |
| local_field += self.J_z[x, y, z] * self.spins[x, y, z+1] |
| |
| |
| dE = 2.0 * S_i * local_field |
| |
| flips_attempted += 1 |
| |
| if dE <= 0.0 or (temp > 0.0 and np.random.uniform(0.0, 1.0) < np.exp(-dE / temp)): |
| self.spins[x, y, z] *= -1 |
| flips_accepted += 1 |
| |
| accept_ratio = flips_accepted / flips_attempted if flips_attempted > 0 else 0.0 |
| return self.calculate_exact_spin_energy(), accept_ratio |
|
|
| def verify_gauge_invariance(self) -> float: |
| """ |
| Fuzzy gauge invariance checking. |
| In spin glasses, the transformation: |
| S_i -> eta_i * S_i, J_ij -> eta_i * eta_j * J_ij (where eta_i in {-1, +1}) |
| is a local symmetry leaving the physical Hamiltonian energy E completely invariant! |
| |
| This method executes a random gauge transform and returns the absolute energy discrepancy. |
| """ |
| L = self.L |
| initial_energy = self.calculate_exact_spin_energy() |
| |
| |
| eta = np.random.choice([-1, 1], size=(L, L, L)) |
| |
| |
| orig_spins = self.spins.copy() |
| orig_J_x = self.J_x.copy() |
| orig_J_y = self.J_y.copy() |
| orig_J_z = self.J_z.copy() |
| orig_h = self.h.copy() |
| |
| |
| self.spins = self.spins * eta |
| self.h = self.h * eta |
| |
| |
| for x in range(L): |
| for y in range(L): |
| for z in range(L): |
| eta_i = eta[x, y, z] |
| if x + 1 < L: |
| self.J_x[x, y, z] *= eta_i * eta[x+1, y, z] |
| if y + 1 < L: |
| self.J_y[x, y, z] *= eta_i * eta[x, y+1, z] |
| if z + 1 < L: |
| self.J_z[x, y, z] *= eta_i * eta[x, y, z+1] |
| |
| |
| gauged_energy = self.calculate_exact_spin_energy() |
| |
| |
| self.spins = orig_spins |
| self.J_x = orig_J_x |
| self.J_y = orig_J_y |
| self.J_z = orig_J_z |
| self.h = orig_h |
| |
| |
| return abs(gauged_energy - initial_energy) |
|
|