Source code for qvartools.hamiltonians.spin.tfim

"""
tfim — Transverse-field Ising model
=====================================

Provides ``TransverseFieldIsing``, the transverse-field Ising model with
tuneable interaction range on a one-dimensional chain with optional
periodic boundary conditions.

Configurations are binary vectors of length ``num_spins`` where
``0 ≡ spin-up (|↑⟩)`` and ``1 ≡ spin-down (|↓⟩)``.
"""

from __future__ import annotations

import torch

from qvartools.hamiltonians.hamiltonian import Hamiltonian

__all__ = [
    "TransverseFieldIsing",
]


[docs] class TransverseFieldIsing(Hamiltonian): r"""Transverse-field Ising model with tuneable interaction range. .. math:: H = -V \sum_{i} \sum_{l=1}^{L} S^z_i\, S^z_{i+l} - h \sum_i S^x_i where *L* controls the range of the ZZ interaction and the sum over *i* respects boundary conditions. Convention: ``config[i] = 0`` → spin-up (S^z = +½), ``config[i] = 1`` → spin-down (S^z = −½). Parameters ---------- num_spins : int Number of spin sites. V : float, optional ZZ interaction strength (default ``1.0``). h : float, optional Transverse field strength (default ``1.0``). L : int, optional Range of the ZZ interaction in lattice units (default ``1`` for nearest-neighbour). periodic : bool, optional Periodic boundary conditions (default ``True``). Examples -------- >>> import torch >>> H = TransverseFieldIsing(num_spins=4, V=1.0, h=0.5) >>> config = torch.tensor([0, 1, 0, 1]) >>> H.diagonal_element(config) tensor(...) >>> energy, state = H.exact_ground_state() """ def __init__( self, num_spins: int, V: float = 1.0, h: float = 1.0, L: int = 1, periodic: bool = True, ) -> None: super().__init__(num_sites=num_spins, local_dim=2) self.V: float = float(V) self.h: float = float(h) self.L: int = int(L) self.periodic: bool = periodic # Precompute interaction pairs self._interaction_pairs: list[tuple[int, int]] = [] for i in range(num_spins): for ell in range(1, self.L + 1): j = i + ell if periodic: j = j % num_spins elif j >= num_spins: break self._interaction_pairs.append((i, j)) # ------------------------------------------------------------------ # Diagonal # ------------------------------------------------------------------
[docs] def diagonal_element(self, config: torch.Tensor) -> torch.Tensor: """Compute ⟨config|H|config⟩ (ZZ interaction with range L). Parameters ---------- config : torch.Tensor Shape ``(num_sites,)``. Returns ------- torch.Tensor Scalar diagonal energy. """ config_f = config.to(dtype=torch.float64) sz = 0.5 - config_f # (num_sites,) energy = 0.0 for i, j in self._interaction_pairs: energy -= self.V * float(sz[i].item()) * float(sz[j].item()) return torch.tensor(energy, dtype=torch.float64, device=config.device)
# ------------------------------------------------------------------ # Off-diagonal # ------------------------------------------------------------------
[docs] def get_connections( self, config: torch.Tensor ) -> tuple[torch.Tensor, torch.Tensor]: """Return off-diagonal connected states from the transverse field. The transverse field ``-h S^x_i`` flips each spin individually with amplitude ``-h/2``. Parameters ---------- config : torch.Tensor Shape ``(num_sites,)``. Returns ------- connected_configs : torch.Tensor Shape ``(n_conn, num_sites)``. matrix_elements : torch.Tensor Shape ``(n_conn,)``. """ if abs(self.h) < 1e-15: return ( torch.empty( (0, self.num_sites), dtype=torch.int64, device=config.device ), torch.empty(0, dtype=torch.float64, device=config.device), ) new_configs: list[torch.Tensor] = [] coeff = -0.5 * self.h for site in range(self.num_sites): new_cfg = config.clone() new_cfg[site] = 1 - int(config[site].item()) new_configs.append(new_cfg) return ( torch.stack(new_configs).to(dtype=torch.int64, device=config.device), torch.full( (self.num_sites,), coeff, dtype=torch.float64, device=config.device ), )