class Ensemble(Optimizer): def __init__(self, atoms, restart=None, logfile=None, trajectory=None, seed=None, verbose=False): Optimizer.__init__(self, atoms, restart, logfile, trajectory=None) atoms.get_forces() atoms.get_potential_energy() if seed is None: seed = np.random.randint(0, 2 ** 31) self.verbose = verbose self.random_state = RandomState(seed) self.starting_atoms = dc(atoms) self.pe = [] self.metadata = {'seed': seed} self.traj = [dc(atoms)] print(self.traj[0].get_momenta()) if trajectory is not None: self.trajectory = Trajectory(trajectory, mode='w') self.trajectory.write(self.traj[-1]) if self.verbose: print('Trajectory written', len(self.traj)) else: self.trajectory = None if verbose: print('trajectory file', self.trajectory) def check_eq(self, eq_steps, tol): ret = np.cumsum(self.pe, dtype=float) ret[eq_steps:] = ret[eq_steps:] - ret[:-eq_steps] ret = ret[eq_steps - 1:] / eq_steps return np.sum(np.gradient(ret[eq_steps:])) < tol def run(self, steps=100000000, eq_steps=None, eq_tol=None, **kwargs): self.metadata['planned iterations'] = steps i = 0 while i < steps: # Check if we are at equilibrium, if we want that if eq_steps is not None: if self.check_eq(eq_steps, eq_tol): break # Verboseness if self.verbose: print('iteration number', i) try: self.step() i += 1 # If we blow up, write the last structure down and exit gracefully except KeyboardInterrupt: print('Interupted, returning data') return self.traj, self.metadata if self.trajectory is not None: self.trajectory.close() return self.traj, self.metadata def step(self): pass def estimate_simulation_duration(self, atoms, iterations): pass
def eos(self, atoms, name): opts = self.opts traj = Trajectory(self.get_filename(name, 'traj'), 'w', atoms) eps = 0.01 strains = np.linspace(1 - eps, 1 + eps, 5) v1 = atoms.get_volume() volumes = strains**3 * v1 energies = [] cell1 = atoms.cell for s in strains: atoms.set_cell(cell1 * s, scale_atoms=True) energies.append(atoms.get_potential_energy()) traj.write(atoms) traj.close() eos = EquationOfState(volumes, energies, opts.eos_type) v0, e0, B = eos.fit() atoms.set_cell(cell1 * (v0 / v1)**(1 / 3), scale_atoms=True) data = {'volumes': volumes, 'energies': energies, 'fitted_energy': e0, 'fitted_volume': v0, 'bulk_modulus': B, 'eos_type': opts.eos_type} return data
def eos(self, atoms, name): args = self.args traj = Trajectory(self.get_filename(name, 'traj'), 'w', atoms) N, eps = args.equation_of_state.split(',') N = int(N) eps = float(eps) / 100 strains = np.linspace(1 - eps, 1 + eps, N) v1 = atoms.get_volume() volumes = strains**3 * v1 energies = [] cell1 = atoms.cell for s in strains: atoms.set_cell(cell1 * s, scale_atoms=True) energies.append(atoms.get_potential_energy()) traj.write(atoms) traj.close() eos = EquationOfState(volumes, energies, args.eos_type) v0, e0, B = eos.fit() atoms.set_cell(cell1 * (v0 / v1)**(1 / 3), scale_atoms=True) data = {'volumes': volumes, 'energies': energies, 'fitted_energy': e0, 'fitted_volume': v0, 'bulk_modulus': B, 'eos_type': args.eos_type} return data
def eos(self, atoms, name): args = self.args traj = Trajectory(self.get_filename(name, 'traj'), 'w', atoms) N, eps = args.equation_of_state.split(',') N = int(N) eps = float(eps) / 100 strains = np.linspace(1 - eps, 1 + eps, N) v1 = atoms.get_volume() volumes = strains**3 * v1 energies = [] cell1 = atoms.cell for s in strains: atoms.set_cell(cell1 * s, scale_atoms=True) energies.append(atoms.get_potential_energy()) traj.write(atoms) traj.close() eos = EquationOfState(volumes, energies, args.eos_type) v0, e0, B = eos.fit() atoms.set_cell(cell1 * (v0 / v1)**(1 / 3), scale_atoms=True) data = { 'volumes': volumes, 'energies': energies, 'fitted_energy': e0, 'fitted_volume': v0, 'bulk_modulus': B, 'eos_type': args.eos_type } return data
def plot(self, out_folder='.', unit_cell='POSCAR', code_name='vasp'): try: from ase.io.trajectory import Trajectory from ase.io import read, iread from ase.visualize import view except ImportError: raise ImportError( "\nThe parent directory of ase package must be included in 'sys.path'" ) if code_name == 'espresso': code_name = 'espresso-in' # aims, espresso-in, vasp atom = read(unit_cell, format=code_name) _current_position_true = atom.positions.copy()[ self.process.unit_cell.atom_true] _mass_weight = self.process.unit_cell.mass_true.reshape( (-1, 3)) / self.process.unit_cell.mass_true.max() for mode_ind in self.mode_inds: traj = Trajectory( out_folder + "/Trajectory_{0}.traj".format(mode_ind), 'w') for ind, x in enumerate( np.linspace(0, 2 * np.pi, self.num_images, endpoint=False), 1): atom.positions[self.process.unit_cell.atom_true] = _current_position_true \ + np.sin(x) * self.mode[mode_ind, :].reshape((-1, 3)).real / np.sqrt(_mass_weight) traj.write(atom) traj.close() atoms = iread(out_folder + "/Trajectory_{0}.traj".format(mode_ind)) view(atoms)
def check_interpolation(initial, final, n_max, interpolation="linear", verbose=True, save=True): ''' Interpolates the provided geometries with n_max total images and checks whether any bond lengths are below sane defaults. Saves the interpolation in interpolation.traj Parameters: initial: Atoms object or string Starting geometry for interpolation. final: Atoms object or string End point geometry for interpolation n_max: integer Desired total number of images for the interpolation including start and end point. interpolation: string "linear" or "idpp". First better for error identification, latter for use in NEB calculation verbose: boolean If verbose output of information is required save: boolean Whether to save the trajectory for transfer on to an NEB calculation ''' from ase.neb import NEB from carmm.analyse.bonds import search_abnormal_bonds from ase.io.trajectory import Trajectory from ase.io import read # Pre-requirements if not isinstance(n_max, int): raise ValueError print("Max number of images must be an integer.") # Make a band consisting of 10 images: images = [initial] images += [initial.copy() for i in range(n_max-2)] images += [final] neb = NEB(images) # Interpolate linearly the potisions of the middle images: neb.interpolate(interpolation, apply_constraint=True) #TODO: Tidy up this horrible mix of if statements. if save: t = Trajectory('interpolation.traj', 'w') flag = True for i in range(0, n_max): if verbose: print("Assessing image", str(i+1) + '.') updated_flag = search_abnormal_bonds(images[i], verbose) if save: t.write(images[i]) if (not updated_flag): flag = updated_flag if save: t.close() return flag
def fit_volume(self, name, atoms, data=None): N, x = self.fit cell0 = atoms.get_cell() v = atoms.get_volume() if x > 0: strains = np.linspace(1 - x, 1 + x, N) else: strains = np.linspace(1 + x / v, 1 - x / v, N)**(1./3) energies = [] traj = Trajectory(self.get_filename(name, 'fit.traj'), 'w') for s in strains: atoms.set_cell(cell0 * s, scale_atoms=True) energies.append(atoms.get_potential_energy()) traj.write(atoms) traj.close() if data is not None: data['strains'] = strains data['energies'] = energies else: assert N % 2 == 1 data = {'energy': energies[N // 2], 'strains': strains, 'energies': energies} return data
def ase_md_playground(): geom = AnaPot.get_geom((0.52, 1.80, 0), atoms=("H", )) atoms = geom.as_ase_atoms() # ase_calc = FakeASE(geom.calculator) # from ase.optimize import BFGS # dyn = BFGS(atoms) # dyn.run(fmax=0.05) import ase from ase import units from ase.io.trajectory import Trajectory from ase.md.velocitydistribution import MaxwellBoltzmannDistribution from ase.md.verlet import VelocityVerlet MaxwellBoltzmannDistribution(atoms, 300 * units.kB) momenta = atoms.get_momenta() momenta[0, 2] = 0. # Zero 3rd dimension atoms.set_momenta(momenta) dyn = VelocityVerlet(atoms, .005 * units.fs) # 5 fs time step. def printenergy(a): """Function to print the potential, kinetic and total energy""" epot = a.get_potential_energy() / len(a) ekin = a.get_kinetic_energy() / len(a) print('Energy per atom: Epot = %.3feV Ekin = %.3feV (T=%3.0fK) ' 'Etot = %.3feV' % (epot, ekin, ekin / (1.5 * units.kB), epot + ekin)) # Now run the dynamics printenergy(atoms) traj_fn = 'asemd.traj' traj = Trajectory(traj_fn, 'w', atoms) dyn.attach(traj.write, interval=5) # dyn.attach(bumms().bimms, interval=1) dyn.run(10000) printenergy(atoms) traj.close() traj = ase.io.read(traj_fn+"@:")#, "r") pos = [a.get_positions() for a in traj] from pysisyphus.constants import BOHR2ANG pos = np.array(pos) / BOHR2ANG calc = geom.calculator calc.plot() ax = calc.ax ax.plot(*pos[:,0,:2].T) plt.show()
def check_interpolation(initial, final, n_max): ''' Interpolates the provided geometries with n_max total images and checks whether any bond lengths below 0.74 Angstrom exist saves the interpolation in interpolation.traj # TODO: incorporate ase.neighborlist.natural_cutoff # for abnormal bond lengths based on typical A-B bonds Parameters: initial: Atoms object or string If a string, e.g. 'initial.traj', a file of this name will be read. Starting geometry for interpolation. final: Atoms object or string If a string, e.g. 'final.traj', a file of this name will be read. End point geometry for interpolation n_max: integer Desired total number of images for the interpolation including start and end point. ''' from ase.neb import NEB from software.analyse.Interatomic_distances.analyse_bonds import search_abnormal_bonds from ase.io.trajectory import Trajectory from ase.io import read # Pre-requirements if isinstance(initial, str) is True: initial = read(initial) if isinstance(final, str) is True: final = read(final) if not isinstance(n_max, int): raise ValueError print("Max number of images must be an integer.") # Make a band consisting of 10 images: images = [initial] images += [initial.copy() for i in range(n_max - 2)] images += [final] neb = NEB(images, climb=True) # Interpolate linearly the potisions of the middle images: neb.interpolate() t = Trajectory('interpolation.traj', 'w') for i in range(0, n_max): print("Assessing image", str(i + 1) + '.') search_abnormal_bonds(images[i]) t.write(images[i]) t.close()
def test_trajectory_heterogeneous(): from ase.constraints import FixAtoms, FixBondLength from ase.build import molecule, bulk from ase.io.trajectory import Trajectory, get_header_data from ase.io import read a0 = molecule('H2O') a1 = a0.copy() a1.rattle(stdev=0.5) a2 = a0.copy() a2.set_masses() a2.center(vacuum=2.0) a2.rattle(stdev=0.2) a3 = molecule('CH3CH2OH') a4 = bulk('Au').repeat((2, 2, 2)) a5 = bulk('Cu').repeat((2, 2, 3)) # Add constraints to some of the images: images = [a0, a1, a2, a3, a4, a5] for i, img in enumerate(images[3:]): img.set_constraint(FixAtoms(indices=range(i + 3))) if i == 2: img.constraints.append(FixBondLength(5, 6)) traj = Trajectory('out.traj', 'w') for i, img in enumerate(images): traj.write(img) print(i, traj.multiple_headers) assert traj.multiple_headers == (i >= 2) traj.close() rtraj = Trajectory('out.traj') newimages = list(rtraj) assert len(images) == len(newimages) for i in range(len(images)): assert images[i] == newimages[i], i h1 = get_header_data(images[i]) h2 = get_header_data(newimages[i]) print(i, images[i]) print(h1) print(h2) print() # assert headers_equal(h1, h2) # Test append mode: with Trajectory('out.traj', 'a') as atraj: atraj.write(molecule('H2')) atraj.write(molecule('H2')) read('out.traj', index=':')
def test_md(): import numpy as np import unittest from ase.units import Ry, Ha from ase.calculators.openmx import OpenMX from ase.io.trajectory import Trajectory from ase.optimize import QuasiNewton from ase.constraints import UnitCellFilter from ase.calculators.calculator import PropertyNotImplementedError from ase import Atoms """ Only OpenMX 3.8 or higher version pass this test""" bud = Atoms('CH4', np.array([ [0.000000, 0.000000, 0.100000], [0.682793, 0.682793, 0.682793], [-0.682793, -0.682793, 0.68279], [-0.682793, 0.682793, -0.682793], [0.682793, -0.682793, -0.682793]]), cell=[10, 10, 10]) calc = OpenMX( label='ch4', xc='GGA', energy_cutoff=300 * Ry, convergence=1e-4 * Ha, # Use 'C_PBE19' and 'H_PBE19' for version 3.9 definition_of_atomic_species=[['C', 'C5.0-s1p1', 'C_PBE13'], ['H', 'H5.0-s1', 'H_PBE13']], kpts=(4, 4, 4), eigensolver='Band' ) bud.set_calculator(calc) try: bud.get_stress() except PropertyNotImplementedError as err: raise unittest.SkipTest(err) traj = Trajectory('example.traj', 'w', bud) ucf = UnitCellFilter(bud, mask=[True, True, False, False, False, False]) dyn = QuasiNewton(ucf) dyn.attach(traj.write) dyn.run(fmax=0.1) bud.get_potential_energy() # XXX maybe assert something? traj.close()
def plot(self, out_folder='.', unit_cell='POSCAR', code_name='vasp'): """ Visualize phonon Mode using modules of the `Atomic Simulation Environment (ASE) <https://wiki.fysik.dtu.dk/ase/index.html>`_. :param out_folder: Folder path for **Trajectory.traj** to be stored, defaults to . :type out_folder: str :param unit_cell: Path of unit cell input file, defaults to POSCAR :type unit_cell: str :param code_name: Specification of the file-format by a DFT program, defaults to vasp :type code_name: str """ try: from ase.io.trajectory import Trajectory from ase.io import read, iread from ase.visualize import view except ImportError: raise ImportError( "\nThe parent directory of ase package must be included in 'sys.path'" ) if code_name == 'espresso': code_name = 'espresso-in' # aims, espresso-in, vasp atom = read(unit_cell, format=code_name) _current_position_true = np.transpose( atom.positions.copy()[self.process.unit_cell.atom_true, :]) _mass_weight = np.transpose( self.process.unit_cell.mass_true.reshape( (-1, 3))) / self.process.unit_cell.mass_true.max() for mode_ind in self.mode_inds: traj = Trajectory( out_folder + "/Trajectory_{0}.traj".format(mode_ind), 'w') for _, x in enumerate( np.linspace(0, 2 * np.pi, self.num_images, endpoint=False), 1): atom.positions[self.process.unit_cell.atom_true, :] = \ np.transpose(_current_position_true + np.sin(x) * np.transpose(self.mode[mode_ind, :].reshape((-1, 3)).real) / np.sqrt(_mass_weight)) # np.sin(x + np.dot(_q, _current_position_true)) traj.write(atom) traj.close() atoms = iread(out_folder + "/Trajectory_{0}.traj".format(mode_ind)) view(atoms)
def write_mode(self, n=None, kT=units.kB * 300, nimages=30): """Write mode number n to trajectory file. If n is not specified, writes all non-zero modes.""" if n is None: for index, energy in enumerate(self.get_energies()): if abs(energy) > 1e-5: self.write_mode(n=index, kT=kT, nimages=nimages) return mode = self.get_mode(n) * sqrt(kT / abs(self.hnu[n])) if self.imagetype == 'atoms': p = self.atom.positions.copy() n %= 3 * len(self.free_atoms) traj = Trajectory('%s.%d.traj' % (self.name, n), 'w') for x in np.linspace(0, 2 * pi, nimages, endpoint=False): self.atom.set_positions(p + sin(x) * mode) traj.write(self.atom) self.atom.set_positions(p) traj.close()
def write_mode(self, n=None, kT=units.kB * 300, nimages=30): """Write mode number n to trajectory file. If n is not specified, writes all non-zero modes.""" if n is None: for index, energy in enumerate(self.get_energies()): if abs(energy) > 1e-5: self.write_mode(n=index, kT=kT, nimages=nimages) return mode = self.get_mode(n) * sqrt(kT / abs(self.hnu[n])) p = self.atoms.positions.copy() n %= 3 * len(self.indices) traj = Trajectory('%s.%d.traj' % (self.name, n), 'w') calc = self.atoms.get_calculator() self.atoms.set_calculator() for x in np.linspace(0, 2 * pi, nimages, endpoint=False): self.atoms.set_positions(p + sin(x) * mode) traj.write(self.atoms) self.atoms.set_positions(p) self.atoms.set_calculator(calc) traj.close()
def make_inspection_traj(self, points=10, filename=None): """Make trajectory file for the vibrational mode for inspection""" if filename is None: filename = self.an_filename + '_inspect.traj' traj = Trajectory(filename, mode='w', atoms=self.atoms) old_pos = self.atoms.positions.copy() calc = self.atoms.get_calculator() self.atoms.set_calculator() displacements = self.get_initial_displacements(displacements=points) for displacement in displacements: new_pos = self.get_displacement_positions(displacement) self.atoms.set_positions(new_pos) traj.write(self.atoms) self.atoms.set_positions(old_pos) self.atoms.set_calculator(calc) traj.close()
def make_inspection_traj(self, points=10, filename=None): """Make trajectory file for the vibrational mode for inspection""" if filename is None: filename = self.an_filename+'_inspect.traj' traj = Trajectory(filename, mode='w', atoms=self.atoms) old_pos = self.atoms.positions.copy() calc = self.atoms.get_calculator() self.atoms.set_calculator() displacements = self.get_initial_displacements(displacements=points) for displacement in displacements: new_pos = self.get_displacement_positions(displacement) self.atoms.set_positions(new_pos) traj.write(self.atoms) self.atoms.set_positions(old_pos) self.atoms.set_calculator(calc) traj.close()
def make_inspection_traj(self, num_displacements=10, filename=None): """Make trajectory file for translational mode to inspect""" if filename is None: filename = self.an_filename + '_inspect.traj' traj = Trajectory(filename, mode='w', atoms=self.atoms) old_pos = self.atoms.positions.copy() calc = self.atoms.get_calculator() self.atoms.set_calculator() angles = self.get_initial_angles(nsamples=num_displacements) for angle in angles: new_pos = self.get_rotate_positions(angle) self.atoms.set_positions(new_pos) traj.write(self.atoms) self.atoms.set_positions(old_pos) self.atoms.set_calculator(calc) traj.close()
def concatenate_trajs(directory, traj): """Concantenate trajectories Merge all the history and trajectory files in one full trajectory of relaxation Arguments: directory {str} -- the directory with unfinished calculation traj {trajectory name} -- name of trajectory file """ history_trajs = [ i for i in os.listdir(directory) if "history" in i ] temp_traj = Trajectory(os.path.join(directory, "temp.traj"), "a") for t in history_trajs: tt = Trajectory(os.path.join(directory, t)) # Merge history trajectories without clashed last steps in each # Since the first step and the last step in the trajectories are the same for at in tt[:-1]: temp_traj.write(at) tt.close() last_traj = Trajectory(os.path.join(directory, traj)) for at in last_traj: temp_traj.write(at) last_traj.close() temp_traj.close() os.rename( os.path.join(directory, "temp.traj"), os.path.join(directory, traj), ) # Cleaning up for i in history_trajs: os.remove(os.path.join(directory, i))
def append_equilibrium_trajectory(self,weight,calc,traj,comment=None,label=None,color=None): """ Calculates the V'rep(r) from a given equilibrium trajectory. The trajectory is set of three (or more, albeit not necessary) frames where atoms move near their equilibrium structure. To first approximation, the energies of these frames ARE THE SAME. This method is then equivalent to append_energy_curve method for given trajectory, with a flat energy curve. * Atoms should move as parallel to the fitted bonds as possible. * Amplitude should be small enough (say, 0.01 Angstroms) parameters: =========== weight: fitting weight calc: Hotbit calculator (remember charge and k-points) traj: filename for ASE trajectory (energies need not be defined) comment: fitting comment for par-file (replaced by comment if None) label: plotting label (replaced by comment if None) color: plotting color """ traj1 = Trajectory(traj) atoms2 = traj1[0].copy() calc2 = NullCalculator() atoms2.set_calculator(calc2) tmpfile = '_tmp.traj' traj2 = Trajectory(tmpfile,'w',atoms2) for atoms1 in traj1: atoms2.set_positions(atoms1.get_positions()) atoms2.set_cell( atoms1.get_cell() ) atoms2.get_potential_energy() traj2.write() traj2.close() self.append_energy_curve(weight,calc,tmpfile,comment,label,color) os.remove(tmpfile) if os.path.isfile(tmpfile+'.bak'): os.remove(tmpfile+'.bak')
def run(self, fmax=0.05, maxstep=0.001, steps=1): if not steps: return if self.log_path: log = open(self.log_path, 'w') if self.traj_path: traj = Trajectory(self.traj_path, 'w') for step in range(steps): r0 = deepcopy(self.atoms.positions) f = self.atoms.get_forces() # -dE/dx m_f = np.linalg.norm(f, axis=1).max() nrg = self.atoms.get_potential_energy() if self.log_path: log.write(f'step {step}, nrg: {nrg}, max_f: {m_f}\n') else: print(f'step {step}, nrg: {nrg}, max_f: {m_f}') if self.traj_path: traj.write(self.atoms) if m_f < fmax: break dr = self.lr * f print(np.max(dr)) # check the max length of dr max_step_length = np.linalg.norm(dr, axis=1).max() if max_step_length > maxstep: scale = maxstep / max_step_length dr = dr * scale r = r0 + dr self.atoms.set_positions(r) if self.traj_path: traj.write(self.atoms) if self.log_path: log.close() if self.traj_path: traj.close()
def write_h_spacemodes(self, n=None, kT=units.kB * 300, nimages=30): """Write mode number n to trajectory file. If n is not specified, writes all non-zero modes.""" if n is None: for index, energy in enumerate(self.hnu_h_post): if abs(energy) > 1e-5: self.write_h_spacemodes(n=index, kT=kT, nimages=nimages) return mode = self.reduced_h_modes[n] * np.sqrt(kT / abs(self.hnu_h_post[n])) p = self.atoms.positions.copy() traj = Trajectory('%sHarmonic_%02d.traj' % (self.pre_names, n), 'w') calc = self.atoms.get_calculator() self.atoms.set_calculator() for x in np.linspace(0, 2 * np.pi, nimages, endpoint=False): pos_delta = np.zeros_like(p) pos_delta[self.vib.indices] += (np.sin(x) * mode.reshape( (len(self.vib.indices), 3))) self.atoms.set_positions(p + pos_delta) traj.write(self.atoms) self.atoms.set_positions(p) self.atoms.set_calculator(calc) traj.close()
def write_h_spacemodes(self, n=None, kT=units.kB * 300, nimages=30): """Write mode number n to trajectory file. If n is not specified, writes all non-zero modes.""" if n is None: for index, energy in enumerate(self.hnu_h_post): if abs(energy) > 1e-5: self.write_h_spacemodes(n=index, kT=kT, nimages=nimages) return mode = self.reduced_h_modes[n] * np.sqrt(kT / abs(self.hnu_h_post[n])) p = self.atoms.positions.copy() traj = Trajectory('%sHarmonic_%02d.traj' % (self.pre_names, n), 'w') calc = self.atoms.get_calculator() self.atoms.set_calculator() for x in np.linspace(0, 2 * np.pi, nimages, endpoint=False): pos_delta = np.zeros_like(p) pos_delta[self.vib.indices] += ( np.sin(x) * mode.reshape((len(self.vib.indices), 3))) self.atoms.set_positions(p + pos_delta) traj.write(self.atoms) self.atoms.set_positions(p) self.atoms.set_calculator(calc) traj.close()
def fit_bond_length(self, name, atoms, data=None): N, x = self.fit d0 = atoms.get_distance(0, 1) distances = np.linspace(d0 * (1 - x), d0 * (1 + x), N) energies = [] traj = Trajectory(self.get_filename(name, 'fit.traj'), 'w') for d in distances: atoms.set_distance(0, 1, d) energies.append(atoms.get_potential_energy()) self.check_occupation_numbers(atoms) traj.write(atoms) traj.close() if data is not None: data['distances'] = distances data['energies'] = energies else: assert N % 2 == 1 data = {'energy': energies[N // 2], 'distances': distances, 'energies': energies} return data
def make_inspection_traj( self, num_displacements=10, filename=None): """Make trajectory file for translational mode to inspect""" if filename is None: filename = self.an_filename+'_inspect.traj' traj = Trajectory(filename, mode='w', atoms=self.atoms) old_pos = self.atoms.positions.copy() calc = self.atoms.get_calculator() self.atoms.set_calculator() angles = self.get_initial_angles(nsamples=num_displacements) for angle in angles: new_pos = self.get_rotate_positions(angle) self.atoms.set_positions(new_pos) traj.write(self.atoms) self.atoms.set_positions(old_pos) self.atoms.set_calculator(calc) traj.close()
def write(self, filename): traj = Trajectory(filename, 'w', self) traj.write() traj.close()
def get_mulliken_charges(self, initial: Atoms, verbose=True): ''' This function is used to retrieve atomic charges using Mulliken charge decomposition as implemented in FHI-aims. A new trajectory file containing the charges Args: initial: Atoms Atoms object containing structural information for the calculation verbose: bool Flag for turning off printouts in the code Returns: Atoms object with charges appended ''' from ase.io.trajectory import Trajectory from carmm.analyse.mulliken import extract_mulliken_charge '''Setup initial parameters''' params = self.params hpc = self.hpc basis_set = self.basis_set self.initial = initial dimensions = sum(self.initial.pbc) '''Parent directory''' parent_dir = os.getcwd() '''Read the geometry''' if not self.filename: self.filename = self.initial.get_chemical_formula() filename = self.filename assert type(filename) == str, "Invalid type, filename should be string" counter, subdirectory_name = self._restart_setup( "Charges", self.filename) '''Check for previously completed calculation''' if os.path.exists( os.path.join(subdirectory_name[:-1] + str(counter - 1), filename + "_charges.traj")): file_location = os.path.join( subdirectory_name[:-1] + str(counter - 1), filename + "_charges.traj") self.initial = read(file_location) if verbose: print("Previously calculated structure has been found at", file_location) return self.initial out = str(counter) + "_" + str(filename) + ".out" '''Set the environment variables for geometry optimisation''' set_aims_command(hpc=hpc, basis_set=basis_set, defaults=2020) '''Request Mulliken charge decomposition''' params["output"] = ["Mulliken_summary"] os.makedirs(subdirectory_name, exist_ok=True) os.chdir(subdirectory_name) with _calc_generator(params, out_fn=out, dimensions=dimensions, forces=False)[0] as calculator: if not self.dry_run: self.initial.calc = calculator else: self.initial.calc = EMT() self.initial.get_potential_energy() if not self.dry_run: charges = extract_mulliken_charge(out, len(self.initial)) else: charges = initial.get_charges() self.initial.set_initial_charges(charges) traj = Trajectory(filename + "_charges.traj", 'w') traj.write(self.initial) traj.close() os.chdir(parent_dir) return self.initial
def aims_optimise(self, atoms: Atoms, fmax: float = 0.01, post_process: str = None, relax_unit_cell: bool = False, restart: bool = True, verbose: bool = True): ''' The function needs information about structure geometry (model), name of hpc system to configure FHI-aims environment variables (hpc). Separate directory is created with a naming convention based on chemical formula and number of restarts, n (opt_formula_n), ensuring that no outputs are overwritten in ASE/FHI-aims. The geometry optimisation is restarted from a new Hessian each 80 steps in BFGS algorithm to overcome deep potential energy local minima with fmax above convergence criteria. One can choose the type of phase of the calculation (gas, surface, bulk) and request a post_processing calculation with a larger basis set. PARAMETERS: params: dict Dictionary containing user's calculator FHI-aims parameters atoms: Atoms object Contains the geometry information for optimisation fmax: float Force convergence criterion for geometry optimisation, i.e. max forces on any atom in eV/A post_process: str or None Basis set to be used for post_processing if energy calculation using a larger basis set is required relax_unit_cell: bool True requests a strain filter unit cell relaxation restart: bool Request restart from previous geometry if True (True by default) Returns a list containing the model with data calculated using light and tight settings: [model_light, model_tight] ''' from ase.io import read from ase.io.trajectory import Trajectory from carmm.analyse.forces import is_converged from ase.optimize import BFGS '''Setup initial parameters''' params = self.params hpc = self.hpc basis_set = self.basis_set self.initial = atoms dimensions = sum(self.initial.pbc) i_geo = atoms.copy() i_geo.calc = atoms.calc '''parent directory''' parent_dir = os.getcwd() '''Read the geometry''' if not self.filename: self.filename = self.initial.get_chemical_formula() filename = self.filename counter, subdirectory_name = self._restart_setup("Opt", self.filename, restart, verbose=verbose) out = str(counter) + "_" + str(filename) + ".out" '''Perform calculation only if required''' if is_converged(atoms, fmax): if verbose: print("The forces are below", fmax, "eV/A. No calculation required.") self.model_optimised = self.atoms elif is_converged(self.initial, fmax): if verbose: print("The forces are below", fmax, "eV/A. No calculation required.") self.model_optimised = self.initial self.initial = i_geo else: os.makedirs(subdirectory_name, exist_ok=True) os.chdir(subdirectory_name) '''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) '''Occasional optimizer restarts will prevent the calculation from getting stuck in deep local minimum''' opt_restarts = 0 '''Perform DFT calculations for each filename''' with _calc_generator( params, out_fn=out, dimensions=dimensions, relax_unit_cell=relax_unit_cell)[0] as calculator: if not self.dry_run: self.initial.calc = calculator else: self.initial.calc = EMT() while not is_converged(self.initial, fmax): if relax_unit_cell: from ase.constraints import StrainFilter unit_cell_relaxer = StrainFilter(self.initial) opt = BFGS(unit_cell_relaxer, trajectory=str(counter) + "_" + filename + "_" + str(opt_restarts) + ".traj", alpha=70.0) else: opt = BFGS(self.initial, trajectory=str(counter) + "_" + filename + "_" + str(opt_restarts) + ".traj", alpha=70.0) opt.run(fmax=fmax, steps=80) opt_restarts += 1 self.model_optimised = read( str(counter) + "_" + filename + "_" + str(opt_restarts - 1) + ".traj") os.chdir(parent_dir) self.initial = i_geo if post_process: if verbose: print("Commencing calculation using", post_process, "basis set.") model_pp = self.model_optimised.copy() '''Set environment variables for a larger basis set - converged electronic structure''' subdirectory_name_tight = subdirectory_name + "_" + post_process os.makedirs(subdirectory_name_tight, exist_ok=True) os.chdir(subdirectory_name_tight) set_aims_command(hpc=hpc, basis_set=post_process, defaults=2020, nodes_per_instance=self.nodes_per_instance) '''Recalculate the structure using a larger basis set in a separate folder''' with _calc_generator(params, out_fn=str(self.filename) + "_" + post_process + ".out", forces=False, dimensions=dimensions)[0] as calculator: if not self.dry_run: model_pp.calc = calculator else: model_pp.calc = EMT() model_pp.get_potential_energy() traj = Trajectory(self.filename + "_" + post_process + ".traj", "w") traj.write(model_pp) traj.close() '''Go back to the parent directory to finish the loop''' os.chdir(parent_dir) '''update the instance with a post_processed model''' self.model_post_processed = model_pp return self.model_optimised, self.model_post_processed
def write_modes(self, q_c, branches=0, kT=units.kB * 300, born=False, repeat=(1, 1, 1), nimages=30, center=False): """Write modes to trajectory file. Parameters: q_c: ndarray q-vector of the modes. branches: int or list Branch index of modes. kT: float Temperature in units of eV. Determines the amplitude of the atomic displacements in the modes. born: bool Include non-analytic contribution to the force constants at q -> 0. repeat: tuple Repeat atoms (l, m, n) times in the directions of the lattice vectors. Displacements of atoms in repeated cells carry a Bloch phase factor given by the q-vector and the cell lattice vector R_m. nimages: int Number of images in an oscillation. center: bool Center atoms in unit cell if True (default: False). """ if isinstance(branches, int): branch_l = [branches] else: branch_l = list(branches) # Calculate modes omega_l, u_l = self.band_structure([q_c], modes=True, born=born) # Repeat atoms atoms = self.atoms * repeat # Center if center: atoms.center() # Here ``Na`` refers to a composite unit cell/atom dimension pos_Nav = atoms.get_positions() # Total number of unit cells N = np.prod(repeat) # Corresponding lattice vectors R_m R_cN = np.indices(repeat).reshape(3, -1) # Bloch phase phase_N = np.exp(2.j * pi * np.dot(q_c, R_cN)) phase_Na = phase_N.repeat(len(self.atoms)) for l in branch_l: omega = omega_l[0, l] u_av = u_l[0, l] # Mean displacement of a classical oscillator at temperature T u_av *= sqrt(kT) / abs(omega) mode_av = np.zeros((len(self.atoms), 3), dtype=complex) # Insert slice with atomic displacements for the included atoms mode_av[self.indices] = u_av # Repeat and multiply by Bloch phase factor mode_Nav = np.vstack(N * [mode_av]) * phase_Na[:, np.newaxis] traj = Trajectory('%s.mode.%d.traj' % (self.name, l), 'w') for x in np.linspace(0, 2 * pi, nimages, endpoint=False): atoms.set_positions( (pos_Nav + np.exp(1.j * x) * mode_Nav).real) traj.write(atoms) traj.close()
atoms sometimes move out of one side of the computational box and in through the other. Such atoms have coordinates outside the box. This facilitates analysis of e.g. diffusion, but can be problematic when plotting. This script reads through a trajectory file, and write a new one where all atoms are mapped into the computational box. If there are axes with free boundary conditions, the corresponding coordinate is left unchanged. SIDE EFFECT: All energies, forces and stresses are removed (yes, this can be considered as a bug!) """ import sys from ase.io.trajectory import Trajectory if len(sys.argv) != 3: print(__doc__) sys.exit(1) infile = Trajectory(sys.argv[1]) outfile = None for atoms in infile: atoms.wrap() atoms.set_calculator(None) # or the singlepointcalculator fails! if outfile is None: outfile = Trajectory(sys.argv[2], 'w') outfile.write(atoms) outfile.close()
class Ensemble(Optimizer): def __init__(self, atoms, restart=None, logfile=None, trajectory=None, seed=None, verbose=False): Optimizer.__init__(self, atoms, restart, logfile, trajectory=None) atoms.get_forces() atoms.get_potential_energy() if seed is None: seed = np.random.randint(0, 2**31) self.verbose = verbose self.random_state = RandomState(seed) self.starting_atoms = dc(atoms) self.pe = [] self.metadata = {'seed': seed} self.traj = [dc(atoms)] print(self.traj[0].get_momenta()) if trajectory is not None: self.trajectory = Trajectory(trajectory, mode='w') self.trajectory.write(self.traj[-1]) if self.verbose: print('Trajectory written', len(self.traj)) else: self.trajectory = None if verbose: print('trajectory file', self.trajectory) def check_eq(self, eq_steps, tol): ret = np.cumsum(self.pe, dtype=float) ret[eq_steps:] = ret[eq_steps:] - ret[:-eq_steps] ret = ret[eq_steps - 1:] / eq_steps return np.sum(np.gradient(ret[eq_steps:])) < tol def run(self, steps=100000000, eq_steps=None, eq_tol=None, **kwargs): self.metadata['planned iterations'] = steps i = 0 while i < steps: # Check if we are at equilibrium, if we want that if eq_steps is not None: if self.check_eq(eq_steps, eq_tol): break # Verboseness if self.verbose: print('iteration number', i) try: self.step() i += 1 # If we blow up, write the last structure down and exit gracefully except KeyboardInterrupt: print('Interupted, returning data') return self.traj, self.metadata if self.trajectory is not None: self.trajectory.close() return self.traj, self.metadata def step(self): pass def estimate_simulation_duration(self, atoms, iterations): pass
chem_inds = ind_dict(supercells[0].get_chemical_symbols()) chem_order = [] for c in unique_chem: chem_order.append(chem_inds[c]) chem_order = np.concatenate(chem_order) # for i in range(len(supercells)): supercells[i] = permute_sequence(supercells[i], chem_order) supertraj.write(supercells[i]) print("\n############# Primitive cell #############") print("Total number of atom = " + str(len(atoms))) print("Cell =") print(atoms.get_cell()) print("\n############# Supercell cell #############") print("Transformation matrix, P ="), print(matrix) print("") print("Total number of atom = " + str(len(super))) print("Cell =") print(super.get_cell()) print("") print("pbc ="), print(super.get_pbc()) print("") supertraj.close() print("Supercell trajectory file :: 'supercell_" + str(s_a1) + "x" + str(s_a2) + "x" + str(s_a3) + "_" + traj_file + "'\n")
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 .pckl), 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.N_c # Set calculator if provided assert self.calc is not None, "Provide calculator in __init__ method" atoms_N.set_calculator(self.calc) # Do calculation on equilibrium structure filename = self.name + '.eq.pckl' fd = opencew(filename) if fd is not None: # if fd is None : means there is already eq.pckl file # Call derived class implementation of __call__ output = self.__call__(atoms_N) # return forces # Write trajectory file ( ssrokyz start ) traj_ss = Trajectory(self.name + ".eq.traj", "w") traj_ss.write(atoms_N) traj_ss.close() # ssrokyz end # Write output to file if rank == 0: pickle.dump(output, fd, protocol=2) sys.stdout.write('Writing %s\n' % filename) fd.close() sys.stdout.flush() # 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: # self.indices was len(atoms) for i in range(3): for sign in [-1, 1]: # Filename for atomic displacement raw_name = '%s.%d%s%s' % ( self.name, a, 'xyz'[i], ' +-'[sign]) # ssrokyz start filename = '%s.pckl' % (raw_name) # ssrokyz end # Wait for ranks before checking for file # barrier() fd = opencew(filename) if fd is None: # Skip if already done sys.stdout.write("Skip " + filename + ". It already exists.") # ssrokyz continue # Update atomic positions atoms_N.positions[offset + a, i] = \ pos[a, i] + sign * self.delta # Call derived class implementation of __call__ output = self.__call__(atoms_N) # Write trajectory file ( ssrokyz start ) traj_ss = Trajectory(raw_name + ".traj", "w") traj_ss.write(atoms_N) traj_ss.close() # ssrokyz end # Write output to file if rank == 0: pickle.dump(output, fd, protocol=2) sys.stdout.write('Writing %s\n' % filename) fd.close() sys.stdout.flush() # Return to initial positions atoms_N.positions[offset + a, i] = pos[a, i]
def write(self, filename): from ase.io.trajectory import Trajectory traj = Trajectory(filename, 'w', self) traj.write() traj.close()
def run(self, fmax=0.035, maxstep=0.04, steps=None): if not steps: return if self.log_path: log = open(self.log_path, 'w') log.write( f'force threshold: {fmax}, std threshold {self.threshold} \n') else: print( f'force threshold: {fmax}, std threshold {self.threshold} \n') if self.traj_path: traj = Trajectory(self.traj_path, 'w') is_certain = True for step in range(steps): r0 = deepcopy(self.atoms.positions) rf = self.atoms.get_forces() for const in self.consts: const.adjust_forces(None, rf) m_f = np.linalg.norm(rf, axis=1).max() nrg = self.atoms.get_potential_energy() nrg_std = self.atoms.calc.results['energy_std'] frs_stds = self.atoms.calc.results['forces_std'] # N_atom * 3 for const in self.consts: const.adjust_forces(None, frs_stds) max_mean_frs_std = frs_stds.mean(axis=1).max() if nrg_std > self.threshold: if step > 1: # when step == 1, it is more possible that our threshold is too low is_certain = False break if m_f < fmax: break if self.log_path: log.write( f'step {step}, nrg: {nrg}, max_f: {m_f}, nrg_std: {nrg_std}, m_f_m_std: {max_mean_frs_std}\n' ) else: print( f'step {step}, nrg: {nrg}, max_f: {m_f}, nrg_std: {nrg_std}, m_f_m_std: {max_mean_frs_std}' ) if self.traj_path: traj.write(self.atoms) # update dr = self.lr * rf # check the max length of dr max_step_length = np.linalg.norm(dr, axis=1).max() if max_step_length > maxstep: scale = maxstep / max_step_length dr = dr * scale r = r0 + dr self.atoms.set_positions(r) if is_certain: # if certain about this step if self.log_path: log.write( f'step {step}, nrg: {nrg}, max_f: {m_f} nrg_std: {nrg_std}, m_f_m_std: {max_mean_frs_std}\n' ) log.write(f'relaxed with certainty \n') else: print( f'step {step}, nrg: {nrg}, max_f: {m_f} nrg_std: {nrg_std}, m_f_m_std: {max_mean_frs_std}' ) print(f'relaxed with certainty \n') if self.traj_path: traj.write(self.atoms) else: if self.log_path: log.write(f'end with uncertain configuration\n') else: print(f'end with uncertain configuration') if self.log_path: log.close() if self.traj_path: traj.close()
def plot_delta(direcs=None, batch_size=1, dft='siesta'): for m in direcs: mol = m rn = ReaxFF(libfile='ffield', direcs=direcs, dft=dft, opt=[], optword='all', batch_size=batch_size, rc_scale='none', interactive=True) molecules = rn.initialize() rn.session(learning_rate=1.0e-10, method='AdamOptimizer') D = rn.get_value(rn.D) Dlp = rn.get_value(rn.Dlp) Dp_ = {} for sp in rn.spec: if rn.nsp[sp] > 0: Dp_[sp] = tf.gather_nd(rn.Deltap, rn.atomlist[sp]) Dp = rn.get_value(Dp_) atlab = rn.lk.atlab traj = Trajectory('delta.traj', 'w') trajp = Trajectory('deltap.traj', 'w') trajlp = Trajectory('deltalp.traj', 'w') natom = molecules[mol].natom d = np.zeros([natom, batch_size]) dp = np.zeros([natom, batch_size]) dlp = np.zeros([natom, batch_size]) cell = rn.cell[mol] for sp in rn.spec: if rn.nsp[sp] > 0: for l, lab in enumerate(atlab[sp]): if lab[0] == mol: i = int(lab[1]) d[i] = D[sp][l] dp[i] = Dp[sp][l] dlp[i] = Dlp[sp][l] for nf in range(batch_size): A = Atoms(symbols=molecules[mol].atom_name, positions=molecules[mol].x[nf], charges=d[:, nf], cell=cell, pbc=(1, 1, 1)) traj.write(A) Ap = Atoms(symbols=molecules[mol].atom_name, positions=molecules[mol].x[nf], charges=dp[:, nf], cell=cell, pbc=(1, 1, 1)) trajp.write(Ap) Alp = Atoms(symbols=molecules[mol].atom_name, positions=molecules[mol].x[nf], charges=dlp[:, nf], cell=cell, pbc=(1, 1, 1)) trajlp.write(Alp) traj.close() trajp.close() trajlp.close()
def write_modes(self, q_c, branches=0, kT=units.kB*300, born=False, repeat=(1, 1, 1), nimages=30, center=False): """Write modes to trajectory file. Parameters ---------- q_c: ndarray q-vector of the modes. branches: int or list Branch index of modes. kT: float Temperature in units of eV. Determines the amplitude of the atomic displacements in the modes. born: bool Include non-analytic contribution to the force constants at q -> 0. repeat: tuple Repeat atoms (l, m, n) times in the directions of the lattice vectors. Displacements of atoms in repeated cells carry a Bloch phase factor given by the q-vector and the cell lattice vector R_m. nimages: int Number of images in an oscillation. center: bool Center atoms in unit cell if True (default: False). """ if isinstance(branches, int): branch_l = [branches] else: branch_l = list(branches) # Calculate modes omega_l, u_l = self.band_structure([q_c], modes=True, born=born) # Repeat atoms atoms = self.atoms * repeat # Center if center: atoms.center() # Here ``Na`` refers to a composite unit cell/atom dimension pos_Nav = atoms.get_positions() # Total number of unit cells N = np.prod(repeat) # Corresponding lattice vectors R_m R_cN = np.indices(repeat).reshape(3, -1) # Bloch phase phase_N = np.exp(2.j * pi * np.dot(q_c, R_cN)) phase_Na = phase_N.repeat(len(self.atoms)) for l in branch_l: omega = omega_l[0, l] u_av = u_l[0, l] # Mean displacement of a classical oscillator at temperature T u_av *= sqrt(kT) / abs(omega) mode_av = np.zeros((len(self.atoms), 3), dtype=complex) # Insert slice with atomic displacements for the included atoms mode_av[self.indices] = u_av # Repeat and multiply by Bloch phase factor mode_Nav = np.vstack(N * [mode_av]) * phase_Na[:, np.newaxis] traj = Trajectory('%s.mode.%d.traj' % (self.name, l), 'w') for x in np.linspace(0, 2*pi, nimages, endpoint=False): atoms.set_positions((pos_Nav + np.exp(1.j * x) * mode_Nav).real) traj.write(atoms) traj.close()
else: print('... reading %s ...' % liquid_fn) a = read(liquid_fn) # Thermalize with the slow (but correct) potential a.set_calculator(calc) traj = Trajectory(liquid_fn, 'a', a) dyn = Langevin(a, dt1, T1, 1.0/tau1, logfile='-', loginterval=int(dtdump/dt1)) dyn.attach(traj.write, interval=int(dtdump/dt1)) # every 100 fs nsteps = int(teq/dt1)-len(traj)*int(dtdump/dt1) print('Need to run for further {} steps to reach total of {} steps.'.format(nsteps, int(teq/dt1))) if nsteps <= 0: nsteps = 1 dyn.run(nsteps) traj.close() # Write snapshot write(liquid_final_fn, a) else: print('... reading %s ...' % liquid_final_fn) a = read(liquid_final_fn) a.set_calculator(calc) print('=== QUENCH ===') if not os.path.exists(quench_final_fn): if os.path.exists(quench_fn): print('... reading %s ...' % quench_fn) a = read(quench_fn) a.set_calculator(calc)
through the other. Such atoms have coordinates outside the box. This facilitates analysis of e.g. diffusion, but can be problematic when plotting. This script reads through a trajectory file, and write a new one where all atoms are mapped into the computational box. If there are axes with free boundary conditions, the corresponding coordinate is left unchanged. SIDE EFFECT: All energies, forces and stresses are removed (yes, this can be considered as a bug!) """ from __future__ import print_function import sys from ase.io.trajectory import Trajectory if len(sys.argv) != 3: print(__doc__) sys.exit(1) infile = Trajectory(sys.argv[1]) outfile = None for atoms in infile: atoms.set_scaled_positions(atoms.get_scaled_positions()) atoms.set_calculator(None) # or the singlepointcalculator fails! if outfile is None: outfile = Trajectory(sys.argv[2], "w") outfile.write(atoms) outfile.close()