if rank in ranks: calc = GPAW(mode=PW(500), xc='PBE', basis='dzp', maxiter=99, kpts=(2, 2, 1), txt='neb{}.txt'.format(j), communicator=ranks) image.set_calculator(calc) images.append(image) images.append(final) # Initialize neb object neb = NEB(images, k=0.5, parallel=True, method='eb', climb=False) #neb.interpolate() # Converge path roughly before invoking the CI method qn = BFGS(neb, trajectory='neb_rough.traj') qn.run(fmax=0.15) # Turn on CI neb.climb = True # Fully converge the path including an image climbing to the saddle point qn = BFGS(neb, trajectory='neb_CI.traj') qn.run(fmax=0.09) write('neb_done.traj', images)
for i in range(nimages): images.append(images[0].copy()) images[-1].positions[6, 1] = 2 - images[0].positions[6, 1] neb = NEB(images) neb.interpolate() if 0: # verify that initial images make sense from ase.visualize import view view(neb.images) for image in images: image.set_calculator(MorsePotential()) dyn = BFGS(neb, trajectory='mep.traj') # , logfile='mep.log') dyn.run(fmax=fmax) for a in neb.images: print(a.positions[-1], a.get_potential_energy()) neb.climb = True dyn.run(fmax=fmax) # Check NEB tools. nt_images = read('mep.traj@-4:') nebtools = NEBtools(nt_images) nt_fmax = nebtools.get_fmax(climb=True) Ef, dE = nebtools.get_barrier() print(Ef, dE, fmax, nt_fmax) assert nt_fmax < fmax assert abs(Ef - 1.389) < 0.001
def aie_ml_neb(neb, ml_calc, steps=150, ml_steps=150, t_mep=0.3, t_ci=0.01, t_ci_on=1.0, r_max=None, t_mep_ml=None, callback_after_ml_neb=None): """ Koistinen et al. J. Chem. Phys. 147, 152720 (2017) """ images = neb.images # save initial path as the machine learning NEB run is always restarted # from the initial path. initial_path = [image.get_positions().copy() for image in images] if r_max is None: # Koistinen et al. suggest half of the length of the initial path for # r_max: r_max = 0.5 * sum([ distance(images[i - 1], images[i]) for i in range(1, len(images)) ]) print('r_max = %.2f' % r_max) # Default value of the threshold for the MEP on the machine learning # surface following Koistinen et al. t_mep_ml = t_mep_ml or 0.1 * t_ci # Add first and last image to the training data for image in (images[0], images[-1]): training_image = image.copy() training_image.set_calculator( SinglePointCalculator( training_image, forces=image.get_forces(apply_constraint=False), energy=image.get_potential_energy())) ml_calc.add_data(training_image) ml_images = [image.copy() for image in images] [ml_image.set_calculator(copy(ml_calc)) for ml_image in ml_images] ml_neb = NEB( ml_images, k=neb.k, climb=neb.climb, method=neb.method, remove_rotation_and_translation=neb.remove_rotation_and_translation) for i_step in range(steps): # Step A: # evaluate intermediate images and add them to the training_image data for image, ml_image in zip(images[1:-1], ml_images[1:-1]): # Update image positions image.set_positions(ml_image.get_positions()) training_image = image.copy() training_image.set_calculator( SinglePointCalculator( training_image, forces=image.get_forces(apply_constraint=False), energy=image.get_potential_energy())) ml_calc.add_data(training_image) # Step B: # Reshape forces into N_intermediate_images x N_atoms x 3 forces = neb.get_forces().reshape((len(ml_neb.images) - 2, -1, 3)) print('Maximum force per image after %d evaluations of the band:' % (i_step + 1)) print(np.sqrt((forces**2).sum(axis=2).max(axis=1))) # Step C: # This differs from Koistinen et al. by checking the maximum force # on any atom and not the norm of the force vector max_force = np.sqrt((forces**2).sum(axis=2).max()) # Use imax-1 since forces only contains intermediate images ci_force = np.sqrt((forces[neb.imax - 1, :, :]**2).sum(axis=1).max()) print('Maximum force: ', max_force) print('Force on climbing image: ', ci_force) if max_force < t_mep and ci_force < t_ci: # Converged return True # Step D: ml_calc.fit() params = ml_calc.get_params() # Step E: for ml_image, init_pos in zip(ml_images, initial_path): # Update calculator ml_image.calc.set_params(**params) # Reset positions to inital path ml_image.set_positions(init_pos.copy()) ml_neb.climb = False _relaxation_phase(ml_neb, ml_calc, ml_steps, t_mep_ml, t_ci_on, r_max) if callback_after_ml_neb is not None: callback_after_ml_neb(images, ml_images, ml_calc) # No convergence reached: return False
def run_mla_neb(neb, ml_calc, optimizer=FIRE, steps=100, f_max=0.05, f_max_ml=None, f_max_ml_ci=None, steps_ml=150, steps_ml_ci=150, r_max=None, callback_after_ml_neb=None): """ """ images = neb.images N_atoms = len(images[0]) # save initial path as the machine learning NEB run is always restarted # from the initial path. initial_path = [image.get_positions().copy() for image in images] # set default values for the machine learning NEB calculations f_max_ml = f_max_ml or 10 * f_max f_max_ml_ci = f_max_ml_ci or 0.5 * f_max if r_max is None: # Koistinen et al. J. Chem. Phys. 147, 152720 (2017) suggest half of # the length of the initial path for r_max: r_max = 0.5 * sum([ distance(images[i - 1], images[i]) for i in range(1, len(images)) ]) print('r_max = %.2f' % r_max) # make a copy of all images and attach a copy of the machine learning # calculator. Add a copy the whole band to the training images ml_images = [image.copy() for image in images] training_images = [image.copy() for image in images] for image, ml_image, training_image in zip(images, ml_images, training_images): ml_image.set_calculator(copy(ml_calc)) training_image.set_calculator( SinglePointCalculator( forces=image.get_forces(apply_constraint=False), energy=image.get_potential_energy(), atoms=training_image)) ml_calc.add_data(training_image) ml_neb = NEB( ml_images, k=neb.k, climb=neb.climb, method=neb.method, remove_rotation_and_translation=neb.remove_rotation_and_translation) for step_i in range(steps): # get the forces on the inner images including the spring forces and # reshape them true_forces = neb.get_forces().reshape( (len(neb.images) - 2, N_atoms, 3)) print('Maximum force per image after %d evaluations of the band:' % (step_i + 1)) print(np.sqrt((true_forces**2).sum(axis=2).max(axis=1))) # Check for convergence, following the default ase convergence # criterion if (true_forces**2).sum(axis=2).max() < f_max**2: print('Converged after %d evaluations of the band.' % (step_i + 1)) break # fit the machine learning model to the training images ml_calc.fit() # save the fitted parameters of the machine learning model params = ml_calc.get_params() # reset machine learning path to initial path and set the parameters of # the individual ml_image calculators to the newly fitted values. for ml_image, init_positions in zip(ml_images, initial_path): ml_image.set_positions(init_positions.copy()) ml_image.calc.set_params(**params) # optimize nudged elastic band on the machine learning PES. Start # without climbing. Should the first run converge. Switch climb = True # and optimize. ml_neb.climb = False if run_neb_on_ml_pes(ml_neb, training_images, optimizer=optimizer, fmax=f_max_ml, steps=steps_ml, r_max=r_max)[0]: print('Switching to climbing image NEB') ml_neb.climb = True run_neb_on_ml_pes(ml_neb, training_images, optimizer=optimizer, fmax=f_max_ml_ci, steps=steps_ml_ci, r_max=r_max) if callback_after_ml_neb is not None: callback_after_ml_neb(images, ml_images, ml_calc) # calculate the inner images at machine learning minimum energy path # and append the results to the training images for image, ml_image in zip(images[1:-1], ml_images[1:-1]): image.set_positions(ml_image.get_positions()) training_image = image.copy() # calculation of the ab initio forces happens at this point because # get_potential_energy() and get_forces() are called for the new # positions of 'image'. training_image.set_calculator( SinglePointCalculator( forces=image.get_forces(apply_constraint=False), energy=image.get_potential_energy(), atoms=training_image)) training_images.append(training_image) ml_calc.add_data(training_image)
def test_neb(plt): from ase import Atoms from ase.constraints import FixAtoms import ase.io from ase.neb import NEB, NEBTools from ase.calculators.morse import MorsePotential from ase.optimize import BFGS, QuasiNewton def calc(): # Common calculator for all images. return MorsePotential() # Create and relax initial and final states. initial = Atoms('H7', positions=[(0, 0, 0), (1, 0, 0), (0, 1, 0), (1, 1, 0), (0, 2, 0), (1, 2, 0), (0.5, 0.5, 1)], constraint=[FixAtoms(range(6))], calculator=calc()) dyn = QuasiNewton(initial) dyn.run(fmax=0.01) final = initial.copy() final.calc = calc() final.positions[6, 1] = 2 - initial.positions[6, 1] dyn = QuasiNewton(final) dyn.run(fmax=0.01) # Run NEB without climbing image. fmax = 0.05 nimages = 4 images = [initial] for index in range(nimages - 2): images += [initial.copy()] images[-1].calc = calc() images += [final] neb = NEB(images) neb.interpolate() with BFGS(neb, trajectory='mep.traj') as dyn: dyn.run(fmax=fmax) # Check climbing image. neb.climb = True dyn.run(fmax=fmax) # Check NEB tools. nt_images = ase.io.read('mep.traj', index='-{:d}:'.format(nimages)) nebtools = NEBTools(nt_images) nt_fmax = nebtools.get_fmax(climb=True) Ef, dE = nebtools.get_barrier() print(Ef, dE, fmax, nt_fmax) assert nt_fmax < fmax assert abs(Ef - 1.389) < 0.001 # Plot one band. nebtools.plot_band() # Plot many (ok, 2) bands. nt_images = ase.io.read('mep.traj', index='-{:d}:'.format(2 * nimages)) nebtools = NEBTools(nt_images) nebtools.plot_bands()