Source code for directupsampling.calculators.effqh

from ase.atoms import Atoms
from ase.calculators.calculator import Calculator
from hiphive import ForceConstants
from hiphive.calculators import ForceConstantCalculator

from directupsampling.effqh.utils import get_ref_atoms
from directupsampling.effqh.fcfit import ForceConstantFit


[docs] class EffQHCalculator(Calculator): """Quasiharmonic ForceConstantCalculator. Implemented as a wrapper around the hiphive ForceConstantCalculator. Uses a caching over volume for instantiated ForceConstantCalculators, which provides acceptable speed when the volume is constant over an MD run, which is the present typical use case. It is not suitable for e.g. NPT MD, where the volume changes at every step. Parameters ---------- ideal_atoms: atoms object whose positions represent the ideal structure used as reference for displacements. fc_fit: force constant-fit coefficients. max_disp: Maximum displacement passed to hiphive ForceConstantCalculator. """ implemented_properties = [ "energy", "forces", ] def __init__( self, reference_atoms: Atoms | list[Atoms] = None, fc_fit: ForceConstantFit = None, max_disp: float = 3.0, ): super().__init__() self.reference_atoms = ( Atoms(reference_atoms) if hasattr(reference_atoms, "get_potential_energy") else [Atoms(_) for _ in reference_atoms] ) self.fc_fit = fc_fit self.max_disp = max_disp self._fc_calc_cache = {}
[docs] def calculate(self, atoms=None, properties=["energy"], *args): super().calculate(atoms, properties, *args) for prop in properties: volume = self.atoms.get_volume() / len(self.atoms) if not volume in self._fc_calc_cache: force_constants = self.fc_fit(volume) reference_atoms = get_ref_atoms(self.reference_atoms, volume) fcs = ForceConstants.from_arrays(reference_atoms, force_constants) fc_calc = ForceConstantCalculator(fcs) self._fc_calc_cache[volume] = fc_calc else: fc_calc = self._fc_calc_cache[volume] result = fc_calc.get_property(prop, self.atoms) self.results[prop] = result