def read(self, method='standard', direction='central'): """Read data from a pre-performed calculation.""" self.timer.start('read') self.timer.start('vibrations') Vibrations.read(self, method, direction) # we now have: # self.H : Hessian matrix # self.im : 1./sqrt(masses) # self.modes : Eigenmodes of the mass weighted Hessian self.om_Q = self.hnu.real # energies in eV self.om_v = self.om_Q # pre-factors for one vibrational excitation with np.errstate(divide='ignore'): self.vib01_Q = np.where(self.om_Q > 0, 1. / np.sqrt(2 * self.om_Q), 0) # -> sqrt(amu) * Angstrom self.vib01_Q *= np.sqrt(u.Ha * u._me / u._amu) * u.Bohr self.timer.stop('vibrations') self.timer.start('excitations') self.init_parallel_read() if not hasattr(self, 'ex0E_p'): if self.overlap: self.read_excitations_overlap() else: self.read_excitations() self.timer.stop('excitations') self.timer.stop('read')
def test_harmonic_vibrations(self, testdir): """Check the numerics with a trivial case: one atom in harmonic well""" rng = np.random.RandomState(42) k = rng.rand() ref_atoms = Atoms('H', positions=np.zeros([1, 3])) atoms = ref_atoms.copy() mass = atoms.get_masses()[0] atoms.calc = ForceConstantCalculator(D=np.eye(3) * k, ref=ref_atoms, f0=np.zeros((1, 3))) vib = Vibrations(atoms, name='harmonic') vib.run() vib.read() expected_energy = ( units._hbar # In J/s * np.sqrt(k # In eV/A^2 * units._e # eV -> J * units.m**2 # A^-2 -> m^-2 / mass # in amu / units._amu # amu^-1 -> kg^-1 )) / units._e # J/s -> eV/s assert np.allclose(vib.get_energies(), expected_energy)
def read(self, method='standard', direction='central'): """Read data from a pre-performed calculation.""" if not hasattr(self, 'modes'): self.timer.start('read vibrations') Vibrations.read(self, method, direction) # we now have: # self.H : Hessian matrix # self.im : 1./sqrt(masses) # self.modes : Eigenmodes of the mass weighted H self.om_r = self.hnu.real # energies in eV self.timer.stop('read vibrations') if not hasattr(self, 'ex0E_p'): self.read_excitations()
def test_pickle_manipulation(self, n2_emt): atoms = n2_emt vib = Vibrations(atoms, name='interrupt') vib.run() disp_file = 'interrupt.1x-.pckl' comb_file = 'interrupt.all.pckl' assert os.path.isfile(disp_file) assert not os.path.isfile(comb_file) with pytest.raises(RuntimeError): vib.split() # Build a combined file assert vib.combine() == 13 # Individual displacements should be gone, combination should exist assert not os.path.isfile(disp_file) assert os.path.isfile(comb_file) # Not allowed to run after data has been combined with pytest.raises(RuntimeError): vib.run() # But reading is allowed vib.read() # Splitting should fail if any split file already exists with open(disp_file, 'w') as f: f.write("hello") with pytest.raises(RuntimeError): vib.split() os.remove(disp_file) # Now split() for real: replace .all.pckl file with displacements vib.split() assert os.path.isfile(disp_file) assert not os.path.isfile(comb_file) # Not allowed to clobber existing combined file with open(comb_file, 'w') as f: f.write("Hello") with pytest.raises(RuntimeError): vib.combine() os.remove(comb_file) # Combining data also fails if some data is missing os.remove('interrupt.1x-.pckl') with pytest.raises(RuntimeError): vib.combine() vib.clean()
def test_json_manipulation(self, testdir, random_dimer): vib = Vibrations(random_dimer, name='interrupt') vib.run() disp_file = Path('interrupt/cache.1x-.json') comb_file = Path('interrupt/combined.json') assert disp_file.is_file() assert not comb_file.is_file() # Should do nothing harmful as files are already split # (It used to raise an error but this is no longer implemented.) vib.split() # Build a combined file assert vib.combine() == 13 # Individual displacements should be gone, combination should exist assert not disp_file.is_file() assert comb_file.is_file() # Not allowed to run after data has been combined with pytest.raises(RuntimeError): vib.run() # But reading is allowed vib.read() # Splitting should fail if any split file already exists with open(disp_file, 'w') as fd: fd.write("hello") with pytest.raises(AssertionError): vib.split() os.remove(disp_file) # Now split() for real: replace .all.json file with displacements vib.split() assert disp_file.is_file() assert not comb_file.is_file()
def test_vibration_on_surface(self, testdir): from ase.build import fcc111, add_adsorbate ag_slab = fcc111('Ag', (4, 4, 2), a=2) n2 = Atoms('N2', positions=[[0., 0., 0.], [0., np.sqrt(2), np.sqrt(2)]]) add_adsorbate(ag_slab, n2, height=1, position='fcc') # Add an interaction between the N atoms hessian_bottom_corner = np.zeros((2, 3, 2, 3)) hessian_bottom_corner[-1, :, -2] = [1, 1, 1] hessian_bottom_corner[-2, :, -1] = [1, 1, 1] hessian = np.zeros((34, 3, 34, 3)) hessian[32:, :, 32:, :] = hessian_bottom_corner ag_slab.calc = ForceConstantCalculator(hessian.reshape( (34 * 3, 34 * 3)), ref=ag_slab.copy(), f0=np.zeros((34, 3))) # Check that Vibrations with restricted indices returns correct Hessian vibs = Vibrations(ag_slab, indices=[-2, -1]) vibs.run() vibs.read() assert_array_almost_equal(vibs.get_vibrations().get_hessian(), hessian_bottom_corner) # These should blow up if the vectors don't match number of atoms vibs.summary() vibs.write_jmol() for i in range(6): # Frozen atoms should have zero displacement assert_array_almost_equal(vibs.get_mode(i)[0], [0., 0., 0.]) # The N atoms should have finite displacement assert np.all(vibs.get_mode(i)[-2:, :])
def vibrate(self, atoms: Atoms, indices: list, read_only=False): ''' This method uses ase.vibrations module, see more for info. User provides the FHI-aims parameters, the Atoms object and list of indices of atoms to be vibrated. Variables related to FHI-aims are governed by the React object. Calculation folders are generated automatically and a sockets calculator is used for efficiency. Work in progress Args: atoms: Atoms object indices: list List of indices of atoms that require vibrations read_only: bool Flag for postprocessing - if True, the method only extracts information from existing files, no calculations are performed Returns: Zero-Point Energy: float ''' '''Retrieve common properties''' basis_set = self.basis_set hpc = self.hpc params = self.params parent_dir = os.getcwd() dimensions = sum(atoms.pbc) if not self.filename: '''develop a naming scheme based on chemical formula''' self.filename = atoms.get_chemical_formula() vib_dir = parent_dir + "/VibData_" + self.filename + "/Vibs" print(vib_dir) vib = Vibrations(atoms, indices=indices, name=vib_dir) '''If a calculation was terminated prematurely (e.g. time limit) empty .json files remain and the calculation of the corresponding stretch modes would be skipped on restart. The line below prevents this''' vib.clean(empty_files=True) '''Extract vibration data from existing files''' if read_only: vib.read() else: '''Calculate required vibration modes''' required_cache = [ os.path.join(vib_dir, "cache." + str(x) + y + ".json") for x in indices for y in ["x+", "x-", "y+", "y-", "y-", "z+", "z-"] ] check_required_modes_files = np.array( [os.path.exists(file) for file in required_cache]) if np.all(check_required_modes_files == True): vib.read() else: '''Set the environment variables for geometry optimisation''' set_aims_command(hpc=hpc, basis_set=basis_set, defaults=2020, nodes_per_instance=self.nodes_per_instance) '''Generate a unique folder for aims calculation''' counter, subdirectory_name = self._restart_setup( "Vib", filename=self.filename, restart=False, verbose=False) os.makedirs(subdirectory_name, exist_ok=True) os.chdir(subdirectory_name) '''Name the aims output file''' out = str(counter) + "_" + str(self.filename) + ".out" '''Calculate vibrations and write the in a separate directory''' with _calc_generator(params, out_fn=out, dimensions=dimensions)[0] as calculator: if not self.dry_run: atoms.calc = calculator else: atoms.calc = EMT() vib = Vibrations(atoms, indices=indices, name=vib_dir) vib.run() vib.summary() '''Generate a unique folder for aims calculation''' if not read_only: os.chdir(vib_dir) vib.write_mode() os.chdir(parent_dir) return vib.get_zero_point_energy()