"""
Triangular Lattice Class
Implements a 2D triangular lattice for general_python.
-----------------------
File : general_python/lattices/triangular.py
Author : Maksymilian Kliczkowski
Date : 2025-12-22
-----------------------
"""
import numpy as np
from typing import Optional
try:
from . import Lattice, LatticeBackend, LatticeBC, LatticeDirection, LatticeType
from .tools.lattice_kspace import HighSymmetryPoints
except ImportError:
raise ImportError("Could not import Lattice base classes. Ensure the module is in the PYTHONPATH.")
#######################################################################################
[docs]
class TriangularLattice(Lattice):
"""
Implementation of the Triangular Lattice (2D).
The triangular lattice is a 2D Bravais lattice with each site having 6 nearest neighbors.
"""
[docs]
def __init__(self, *, dim=2, lx=3, ly=3, lz=1, bc='pbc', **kwargs):
'''
Initialize a Triangular Lattice.
'''
super().__init__(dim, lx, ly, lz, bc, **kwargs)
self._type = LatticeType.TRIANGULAR
self._ns = self.Lx * self.Ly * self.Lz
# Primitive lattice vectors for 2D triangular lattice
self._a1 = np.array([self.a, 0, 0])
self._a2 = np.array([self.a/2, np.sqrt(3)*self.a/2, 0])
self._a3 = np.array([0, 0, self.c])
self._basis = np.array([[0.0, 0.0, 0.0]])
self._delta_x = self._a1
self._delta_y = self._a2
self._delta_z = self._a3
self.init(**kwargs)
def __str__(self):
return f"TRI,{self.bc},d={self.dim},Ns={self.Ns},Lx={self.Lx},Ly={self.Ly},Lz={self.Lz}{self._flux_suffix}"
def __repr__(self):
return self.__str__()
# -----------------------------------------------------------------------
[docs]
def high_symmetry_points(self) -> Optional[HighSymmetryPoints]:
"""
Return high-symmetry points for the triangular lattice Brillouin zone.
"""
return HighSymmetryPoints.triangular_2d()
[docs]
def contains_special_point(self, point, *, tol: float = 1e-12) -> bool:
"""Check if a triangular special point is present in the current k-grid."""
return super().contains_special_point(point, tol=tol)
# -----------------------------------------------------------------------
[docs]
def get_real_vec(self, x: int, y: int, z: int):
"""
Returns the real-space vector for a given (x, y, z) coordinate.
"""
base = x * self._a1 + y * self._a2
return base + self._basis[0] + z * self._a3
[docs]
def get_norm(self, x: int, y: int, z: int):
"""Return the Euclidean norm of integer coordinate offsets."""
return np.sqrt(x**2 + y**2 + z**2)
[docs]
def get_nn_direction(self, site, direction):
"""Return the nearest neighbor associated with a lattice direction."""
# For triangular lattice, directions can be mapped to 0-5 for 6 neighbors
mapping = {LatticeDirection.X: 0, LatticeDirection.Y: 1, LatticeDirection.Z: 2}
idx = mapping.get(direction, -1)
return self._nn[site][idx] if idx >= 0 and idx < len(self._nn[site]) else -1
[docs]
def calculate_nn_in(self, pbcx: bool, pbcy: bool, pbcz: bool):
"""
Calculates the nearest neighbors (NN) for the triangular lattice.
Each site has 6 nearest neighbors in 2D corresponding to the six
lattice-vector displacements::
+a1, -a1, +a2, -a2, +(a1-a2), -(a1-a2)
i.e. cell-coordinate offsets:
(+1,0), (-1,0), (0,+1), (0,-1), (+1,-1), (-1,+1)
Forward bonds are those connecting to a site with *strictly higher*
index so that each bond is counted exactly once.
Parameters
----------
pbcx, pbcy, pbcz : bool
Whether periodic boundary conditions apply along each direction.
"""
Lx, Ly, Lz = self._lx, self._ly, self._lz
Ns = self._ns
self._nn = [[] for _ in range(Ns)]
self._nn_forward = [[] for _ in range(Ns)]
def _bc(val, L, pbc):
if pbc:
return val % L
return val if 0 <= val < L else -1
def _idx(cx, cy, cz):
return (cz * Ly + cy) * Lx + cx
# Six NN offsets (dx, dy) in cell coordinates
nn_offsets = [
(+1, 0), # +a1
(-1, 0), # -a1
( 0, +1), # +a2
( 0, -1), # -a2
(+1, -1), # +(a1 - a2)
(-1, +1), # -(a1 - a2)
]
for i in range(Ns):
cx = i % Lx
cy = (i // Lx) % Ly
cz = i // (Lx * Ly)
for dx, dy in nn_offsets:
nx = _bc(cx + dx, Lx, pbcx)
ny = _bc(cy + dy, Ly, pbcy)
if nx == -1 or ny == -1:
j = -1
else:
j = _idx(nx, ny, cz)
self._nn[i].append(j)
# Forward: only neighbours with strictly higher index
self._nn_forward[i] = [j for j in self._nn[i] if j > i]
return self._nn
[docs]
def calculate_nnn_in(self, pbcx: bool, pbcy: bool, pbcz: bool):
"""
Calculates the next-nearest neighbors (NNN) for the triangular lattice.
NNN are at cell-coordinate offsets::
(+2,0), (-2,0), (0,+2), (0,-2), (+2,-2), (-2,+2),
(+1,+1), (-1,-1), (+2,-1), (-2,+1), (+1,-2), (-1,+2)
"""
Lx, Ly, Lz = self._lx, self._ly, self._lz
Ns = self._ns
self._nnn = [[] for _ in range(Ns)]
self._nnn_forward = [[] for _ in range(Ns)]
def _bc(val, L, pbc):
if pbc:
return val % L
return val if 0 <= val < L else -1
def _idx(cx, cy, cz):
return (cz * Ly + cy) * Lx + cx
nnn_offsets = [
(+2, 0), (-2, 0),
( 0, +2), ( 0, -2),
(+2, -2), (-2, +2),
(+1, +1), (-1, -1),
(+2, -1), (-2, +1),
(+1, -2), (-1, +2),
]
for i in range(Ns):
cx = i % Lx
cy = (i // Lx) % Ly
cz = i // (Lx * Ly)
for dx, dy in nnn_offsets:
nx = _bc(cx + dx, Lx, pbcx)
ny = _bc(cy + dy, Ly, pbcy)
if nx == -1 or ny == -1:
self._nnn[i].append(-1)
else:
self._nnn[i].append(_idx(nx, ny, cz))
self._nnn_forward[i] = [j for j in self._nnn[i] if j > i]
return self._nnn
[docs]
def site_index(self, x, y, z):
"""Convert integer cell coordinates to a linear site index."""
return z * (self.Lx * self.Ly) + y * self.Lx + x
[docs]
@staticmethod
def dispersion(k, a=1.0):
"""
Simple triangular-lattice dispersion approximation:
ω(k) = 2J * [3 - cos(k·a1) - cos(k·a2) - cos(k·(a1 - a2))]
where a1=(a,0), a2=(a/2, √3 a/2).
Accepts k as (2,) or (...,2).
"""
k = np.asarray(k)
a1 = np.array([a, 0.0])
a2 = np.array([a/2.0, np.sqrt(3.0) * a / 2.0])
a3 = a1 - a2
def _omega(kx, ky):
return 2.0 * (3.0 - np.cos(kx * a1[0] + ky * a1[1]) - np.cos(kx * a2[0] + ky * a2[1]) - np.cos(kx * a3[0] + ky * a3[1]))
if k.ndim == 1:
kx, ky = k[0], k[1]
return _omega(kx, ky)
else:
kx = k[..., 0]
ky = k[..., 1]
return _omega(kx, ky)
# ---------------------------------------------------------------------------
#! EOF
# -------------------------------------------------------------------------