Source code for spindynam.dynamics

import numpy as np
from ase.md.md import MolecularDynamics
from ase.units import fs


[docs] class SpinLatticeDynamics(MolecularDynamics): """Coupled Spin-Lattice Dynamics integrator. Following Tranchida et al., J. Comput. Phys. 372 (2018) 406-425. """ def __init__( self, atoms, timestep, trajectory=None, logfile=None, loginterval=1, append_trajectory=False, **kwargs, ): MolecularDynamics.__init__( self, atoms, timestep, trajectory, logfile, loginterval, append_trajectory=append_trajectory, **kwargs, ) self._ensure_spins() def _ensure_spins(self): if "spins" in self.atoms.arrays: return magmoms = self.atoms.get_initial_magnetic_moments() spins = np.zeros((len(self.atoms), 3)) if magmoms.ndim == 1: spins[:, 2] = magmoms else: spins = magmoms.copy() self.atoms.set_array("spins", spins)
[docs] def step(self, forces=None): dt = self.dt # 1. Update momenta (t+dt/2) forces = forces if forces is not None else self.atoms.get_forces(md=True) p = self.atoms.get_momenta() + 0.5 * dt * forces self.atoms.set_momenta(p) # 2. Update spins (t+dt/2) self.update_spins(0.5 * dt) # 3. Update positions (t+dt) r = self.atoms.get_positions() m = self.atoms.get_masses()[:, np.newaxis] self.atoms.set_positions(r + dt * p / m) # 4. Update spins (t+dt) self.update_spins(0.5 * dt) # 5. Update momenta (t+dt) forces = self.atoms.get_forces(md=True) self.atoms.set_momenta(self.atoms.get_momenta() + 0.5 * dt * forces) return forces
[docs] def update_spins(self, dt): """Update spins using a symmetric sequential update (Suzuki-Trotter).""" if not hasattr(self.atoms.calc, "get_magnetic_forces"): raise RuntimeError("Calculator must implement get_magnetic_forces") natoms = len(self.atoms) half_dt_fs = 0.5 * dt / fs # Forward pass (1 to N) for i in range(natoms): self._rotate_single_spin(i, half_dt_fs) # Backward pass (N to 1) for i in range(natoms - 1, -1, -1): self._rotate_single_spin(i, half_dt_fs) self.atoms.calc.results.clear()
def _rotate_single_spin(self, idx, dt_fs): self.atoms.calc.results.clear() omega = self.atoms.calc.get_magnetic_forces(self.atoms) spins = self.atoms.get_array("spins") w, s = omega[idx], spins[idx] w_norm_sq = np.dot(w, w) if w_norm_sq > 1e-30: w_cross_s = np.cross(w, s) w_c_w_c_s = np.cross(w, w_cross_s) denom = 1.0 + 0.25 * dt_fs**2 * w_norm_sq delta_s = (dt_fs * w_cross_s + 0.5 * dt_fs**2 * w_c_w_c_s) / denom s_new = s + delta_s spins[idx] = s_new / np.linalg.norm(s_new) self.atoms.set_array("spins", spins)