def port(picklefile): picklefile = Path(picklefile) name = picklefile.name vibname, key, pckl = name.rsplit('.', 3) assert pckl == 'pckl' cache = MultiFileJSONCache(picklefile.parent / vibname) obj = pickle.loads(picklefile.read_bytes()) if isinstance(obj, np.ndarray): # vibrations dct = {'forces': obj} else: # Infrared forces, dipole = obj assert isinstance(forces, np.ndarray), f'not supported: {type(forces)}' assert isinstance(dipole, np.ndarray), f'not supported: {type(dipole)}' dct = {'forces': forces, 'dipole': dipole} outfilename = cache._filename(key) if key in cache: del cache[key] cache[key] = dct print(f'wrote {picklefile} ==> {outfilename}')
def __init__(self, atoms, calc=None, supercell=(1, 1, 1), name=None, delta=0.01, center_refcell=False): """Init with an instance of class ``Atoms`` and a calculator. Parameters: atoms: Atoms object The atoms to work on. calc: Calculator Calculator for the supercell calculation. supercell: tuple Size of supercell given by the number of repetitions (l, m, n) of the small unit cell in each direction. name: str Base name to use for files. delta: float Magnitude of displacement in Ang. center_refcell: bool Reference cell in which the atoms will be displaced. If False, then corner cell in supercell is used. If True, then cell in the center of the supercell is used. """ # Store atoms and calculator self.atoms = atoms self.calc = calc # Displace all atoms in the unit cell by default self.indices = np.arange(len(atoms)) self.name = name self.delta = delta self.center_refcell = center_refcell self.supercell = supercell self.cache = MultiFileJSONCache('phonons-cache')
class ElphCache: def __init__(self, name): self.cache = MultiFileJSONCache(name) def read(self, displacement: tp.Union[interop.AseDisplacement, str]): d = self.cache[str(displacement)] return ElphDataset(**d) @contextmanager def lock(self, displacement: tp.Union[interop.AseDisplacement, str]): class MyHandle: def __init__(self, handle): self._handle = handle def write(self, data: ElphDataset): self._handle.save(data._asdict()) with self.cache.lock(str(displacement)) as handle: if handle is None: yield None else: yield MyHandle(handle)
class Displacement: """Abstract base class for phonon and el-ph supercell calculations. Both phonons and the electron-phonon interaction in periodic systems can be calculated with the so-called finite-displacement method where the derivatives of the total energy and effective potential are obtained from finite-difference approximations, i.e. by displacing the atoms. This class provides the required functionality for carrying out the calculations for the different displacements in its ``run`` member function. Derived classes must overwrite the ``__call__`` member function which is called for each atomic displacement. """ def __init__(self, atoms, calc=None, supercell=(1, 1, 1), name=None, delta=0.01, center_refcell=False): """Init with an instance of class ``Atoms`` and a calculator. Parameters: atoms: Atoms object The atoms to work on. calc: Calculator Calculator for the supercell calculation. supercell: tuple Size of supercell given by the number of repetitions (l, m, n) of the small unit cell in each direction. name: str Base name to use for files. delta: float Magnitude of displacement in Ang. center_refcell: bool Reference cell in which the atoms will be displaced. If False, then corner cell in supercell is used. If True, then cell in the center of the supercell is used. """ # Store atoms and calculator self.atoms = atoms self.calc = calc # Displace all atoms in the unit cell by default self.indices = np.arange(len(atoms)) self.name = name self.delta = delta self.center_refcell = center_refcell self.supercell = supercell self.cache = MultiFileJSONCache('phonons-cache') def define_offset(self): # Reference cell offset if not self.center_refcell: # Corner cell self.offset = 0 else: # Center cell N_c = self.supercell self.offset = (N_c[0] // 2 * (N_c[1] * N_c[2]) + N_c[1] // 2 * N_c[2] + N_c[2] // 2) return self.offset @property # type: ignore @ase.utils.deprecated('Please use phonons.supercell instead of .N_c') def N_c(self): return self._supercell @property def supercell(self): return self._supercell @supercell.setter def supercell(self, supercell): assert len(supercell) == 3 self._supercell = tuple(supercell) self.define_offset() self._lattice_vectors_array = self.compute_lattice_vectors() @ase.utils.deprecated('Please use phonons.compute_lattice_vectors()' ' instead of .lattice_vectors()') def lattice_vectors(self): return self.compute_lattice_vectors() def compute_lattice_vectors(self): """Return lattice vectors for cells in the supercell.""" # Lattice vectors -- ordered as illustrated in class docstring # Lattice vectors relevative to the reference cell R_cN = np.indices(self.supercell).reshape(3, -1) N_c = np.array(self.supercell)[:, np.newaxis] if self.offset == 0: R_cN += N_c // 2 R_cN %= N_c R_cN -= N_c // 2 return R_cN def __call__(self, *args, **kwargs): """Member function called in the ``run`` function.""" raise NotImplementedError("Implement in derived classes!.") def set_atoms(self, atoms): """Set the atoms to vibrate. Parameters: atoms: list Can be either a list of strings, ints or ... """ assert isinstance(atoms, list) assert len(atoms) <= len(self.atoms) if isinstance(atoms[0], str): assert np.all([isinstance(atom, str) for atom in atoms]) sym_a = self.atoms.get_chemical_symbols() # List for atomic indices indices = [] for type in atoms: indices.extend( [a for a, atom in enumerate(sym_a) if atom == type]) else: assert np.all([isinstance(atom, int) for atom in atoms]) indices = atoms self.indices = indices def _disp(self, a, i, step): from ase.vibrations.vibrations import Displacement as VDisplacement return VDisplacement(a, i, np.sign(step), abs(step), self) def run(self): """Run the calculations for the required displacements. This will do a calculation for 6 displacements per atom, +-x, +-y, and +-z. Only those calculations that are not already done will be started. Be aware that an interrupted calculation may produce an empty file (ending with .json), which must be deleted before restarting the job. Otherwise the calculation for that displacement will not be done. """ # Atoms in the supercell -- repeated in the lattice vector directions # beginning with the last atoms_N = self.atoms * self.supercell # Set calculator if provided assert self.calc is not None, "Provide calculator in __init__ method" atoms_N.calc = self.calc # Do calculation on equilibrium structure eq_disp = self._disp(0, 0, 0) #with self.cache.lock(f'{self.name}.eq') as handle: with self.cache.lock(eq_disp.name) as handle: if handle is not None: output = self(atoms_N) # Write output to file if world.rank == 0: handle.save(output) # Positions of atoms to be displaced in the reference cell natoms = len(self.atoms) offset = natoms * self.offset pos = atoms_N.positions[offset:offset + natoms].copy() # Loop over all displacements for a in self.indices: for i in range(3): for sign in [-1, 1]: disp = self._disp(a, i, sign) #key = '%s.%d%s%s' % (self.name, a, 'xyz'[i], ' +-'[sign]) with self.cache.lock(disp.name) as handle: if handle is None: continue try: atoms_N.positions[offset + a, i] = \ pos[a, i] + sign * self.delta result = self.calculate(atoms_N, disp) handle.save(result) finally: # Return to initial positions atoms_N.positions[offset + a, i] = pos[a, i] def clean(self): """Delete generated json files.""" if isfile(self.name + '.eq.json'): remove(self.name + '.eq.json') for a in self.indices: for i in 'xyz': for sign in '-+': name = '%s.%d%s%s.json' % (self.name, a, i, sign) if isfile(name): remove(name)
def cache(): return MultiFileJSONCache('cache')
def __init__(self, name): self.cache = MultiFileJSONCache(name)