def test_run_md(geom): T = 298.15 seed = 20182503 energy_ref = -20.4717278924 # Obtained with xtb 6.3.2 velocities = get_mb_velocities_for_geom(geom, T=T, seed=seed) calc = geom.calculator geoms = calc.run_md(geom.atoms, geom.cart_coords, t=200, dt=0.1, velocities=velocities) assert len(geoms) == 200 last_geom = geoms[-1] energy = calc.get_energy(last_geom.atoms, last_geom.cart_coords)["energy"] assert energy == pytest.approx(energy_ref)
def test_thermostat(): geom = geom_loader("lib:dynamics/10_water.xyz") from pysisyphus.calculators.XTB import XTB geom.set_calculator(XTB(pal=2)) opt = RFOptimizer(geom, thresh="gau_loose", max_cycles=25) opt.run() T = 298.15 seed = 20182503 v0 = get_mb_velocities_for_geom(geom, T, seed=seed).flatten() # steps = 6000 steps = 250 dt = 0.5 md_kwargs = { "v0": v0, "steps": steps, "dt": dt, "remove_com_v": True, "thermostat": "csvr", "timecon": 25, } res = md(geom, **md_kwargs) # assert dt * steps / 1000 == pytest.approx(res.t) import matplotlib.pyplot as plt E_tot = res.E_tot E_tot -= E_tot.mean() from pysisyphus.constants import AU2KJPERMOL E_tot *= AU2KJPERMOL T = res.T fig, (ax0, ax1) = plt.subplots(nrows=2) ax0.plot(E_tot) ax0.axhline(E_tot.mean()) ax0.set_title("$\Delta$E$_{tot}$ / kJ mol⁻¹") ax1.plot(T) T_mean = T.mean() print("T_mean", T_mean, "K") ax1.axhline(T_mean) ax1.set_title(f"T / K, avg. = {T_mean:.2f} K") plt.tight_layout() plt.show() fig.savefig("md.pdf")
def test_rattle_tip3p(this_dir): geom = geom_loader(this_dir / "output_10.xyz") # geom = geom_loader(this_dir / "output_2.xyz") # geom.jmol() T = 298.15 calc = TIP3P() potentials = [ { "type": "logfermi", "beta": 6, "T": T, "radius": 10, }, ] ext_calc = ExternalPotential(calc, potentials=potentials) geom.set_calculator(ext_calc) constraints = list( it.chain( *[get_water_constraints(i) for i in range(len(geom.atoms) // 3)])) seed = 20200626 v0 = get_mb_velocities_for_geom(geom, T, seed=seed).flatten() md_kwargs = { "v0": v0, # "steps": 100, # "dt": 1, "steps": 250, "dt": 1.5, # "steps": 500, # "dt": 0.25, "constraints": constraints, "constraint_kwargs": { # "remove_com_v": False, }, # "thermostat": "csvr", } # import pdb; pdb.set_trace() md_result = md(geom, **md_kwargs) from pysisyphus.xyzloader import coords_to_trj coords = md_result.coords trj_fn = "md.trj" atoms = geom.atoms coords_to_trj(trj_fn, atoms, coords)
def test_get_mb_velocities_for_geom(remove_com, remove_rot): geom = geom_loader("lib:benzene.xyz") T = 298.15 fixed_dof = 0 if remove_com: fixed_dof += 3 if remove_rot: fixed_dof += 3 v = get_mb_velocities_for_geom(geom, T, remove_com=remove_com, remove_rot=remove_rot) # in Bohr/fs E_kin = kinetic_energy_from_velocities(geom.masses, v) E_ref = kinetic_energy_for_temperature(len(geom.atoms), T, fixed_dof=fixed_dof) # in Bohr assert E_kin == pytest.approx(E_ref)
def test_mb_velocities(): geom = geom_loader("lib:h2o.xyz") geom.set_calculator(PySCF(basis="sto3g")) # Preoptimization opt = RFOptimizer(geom, thresh="gau_tight") opt.run() print() T = 298.15 seed = 20182503 v0 = get_mb_velocities_for_geom(geom, T, seed=seed).flatten() steps = 100 dt = 0.5 res = md(geom, v0, steps, dt) assert dt * steps / 1000 == pytest.approx(res.t) # import pdb; pdb.set_trace() from pysisyphus.xyzloader import coords_to_trj coords = res.coords trj_fn = "md.trj" atoms = geom.atoms coords_to_trj(trj_fn, atoms, coords)
def mdp( geom, steps, dt, term_funcs=None, steps_init=None, E_excess=0.0, displ_length=0.1, epsilon=5e-4, ascent_alpha=0.05, max_ascent_steps=25, max_init_trajs=10, dump=True, seed=None, external_md=False, ): # Sanity checks and forcing some types dt = float(dt) assert dt > 0.0 steps = int(steps) t = dt * steps # assert t > dt if steps_init is None: steps_init = steps // 10 print(f"No 'steps_init' provided! Using {steps_init}") E_excess = float(E_excess) assert E_excess >= 0.0 displ_length = float(displ_length) assert displ_length >= 0.0 if term_funcs is None: term_funcs = {} for k, v in term_funcs.items(): if callable(v): continue elif isinstance(v, str): term_funcs[k] = parse_raw_term_func(v) else: raise Exception(f"Invalid term function '{k}: {v}' encountered!") print(highlight_text("Minimum dynamic path calculation")) if seed is None: # 2**32 - 1 seed = np.random.randint(4294967295) np.random.seed(seed) print(f"Using seed {seed} to initialize the random number generator.\n") E_TS = geom.energy E_tot = E_TS + E_excess # Distribute E_excess evenly on E_pot and E_kin E_pot_diff = 0.5 * E_excess E_pot_desired = E_TS + E_pot_diff print(f"E_TS={E_TS:.6f} au") # Determine transition vector w, v = np.linalg.eigh(geom.hessian) assert w[0] < -1e-8 trans_vec = v[:, 0] # Disable removal of translation/rotation for analytical potentials remove_com_v = remove_rot_v = geom.cart_coords.size > 3 if E_excess == 0.0: print("MDP without excess energy.") # Without excess energy we have to do an initial displacement along # the transition vector to get a non-vanishing gradient. initial_displacement = displ_length * trans_vec x0_plus = geom.coords + initial_displacement x0_minus = geom.coords - initial_displacement v0_zero = np.zeros_like(geom.coords) md_kwargs = { "v0": v0_zero.copy(), "t": t, "dt": dt, "term_funcs": term_funcs, "external": external_md, } geom.coords = x0_plus md_fin_plus = run_md(geom, **md_kwargs) geom.coords = x0_minus md_fin_minus = run_md(geom, **md_kwargs) if dump: dump_coords(geom.atoms, md_fin_plus.coords, "mdp_plus.trj") dump_coords(geom.atoms, md_fin_minus.coords, "mdp_minus.trj") mdp_result = MDPResult( ascent_xs=None, md_init_plus=None, md_init_minus=None, md_fin_plus=md_fin_plus, md_fin_minus=md_fin_minus, ) return mdp_result print(f"E_excess={E_excess:.6f} au, ({E_excess*AU2KJPERMOL:.1f} kJ/mol)") print(f"E_pot,desired=E_TS + {E_pot_diff*AU2KJPERMOL:.1f} kJ/mol") print() # Generate random vector perpendicular to transition vector perp_vec = np.random.rand(*trans_vec.shape) # Zero last element if we have an analytical surface if perp_vec.size == 3: perp_vec[2] = 0 # Orthogonalize vector perp_vec = perp_vec - (perp_vec @ trans_vec) * trans_vec perp_vec /= np.linalg.norm(perp_vec) # Initial displacement from x_TS to x, generating a point with # non-vanishing gradient. x = geom.coords + epsilon * perp_vec geom.coords = x # Do steepest ascent until E_tot is reached E_pot = geom.energy ascent_xs = list() for i in range(max_ascent_steps): ascent_xs.append(geom.coords.copy()) ascent_converged = E_pot >= E_pot_desired if ascent_converged: break gradient = geom.gradient E_pot = geom.energy direction = gradient / np.linalg.norm(gradient) step = ascent_alpha * direction new_coords = geom.coords + step geom.coords = new_coords # calc = geom.calculator # class Opt: # pass # _opt = Opt() # _opt.coords = np.array(ascent_xs) # calc.plot_opt(_opt, show=True) assert ascent_converged, "Steepest ascent didn't converge!" assert (E_tot - E_pot) > 0.0, ( "Potential energy after steepst ascent is greater than the desired " f"total energy ({E_pot:.6f} > {E_tot:.6f}). Maybe try a smaller epsilon? " f"The current value Ɛ={epsilon:.6f} may be too big!") ascent_xs = np.array(ascent_xs) if dump: dump_coords(geom.atoms, ascent_xs, "mdp_ee_ascent.trj") x0 = geom.coords.copy() print(highlight_text("Runninig initialization trajectories", level=1)) for i in range(max_init_trajs): # Determine random momentum vector for the given kinetic energy E_kin = E_tot - E_pot T = temperature_for_kinetic_energy(len(geom.atoms), E_kin) v0 = get_mb_velocities_for_geom(geom, T, remove_com_v=remove_com_v, remove_rot_v=remove_rot_v).flatten() # Zero last element if we have an analytical surface if v0.size == 3: v0[2] = 0 # Run initial MD to check if both trajectories run towards different # basins of attraction. # First MD with positive v0 md_init_kwargs = { "v0": v0.copy(), "steps": steps_init, "dt": dt, "external": external_md, } geom.coords = x0.copy() md_init_plus = run_md(geom, **md_init_kwargs) # Second MD with negative v0 geom.coords = x0.copy() md_init_kwargs["v0"] = -v0.copy() md_init_minus = run_md(geom, **md_init_kwargs) dump_coords(geom.atoms, md_init_plus.coords, f"mdp_ee_init_plus_{i:02d}.trj") dump_coords(geom.atoms, md_init_minus.coords, f"mdp_ee_init_minus_{i:02d}.trj") # Check if both MDs run into different basins of attraction. # We (try to) do this by calculating the overlap between the # transition vector and the normalized vector defined by the # difference between x0 and the endpoint of the respective # test trajectory. Both overlaps should have different sings. end_plus = md_init_plus.coords[-1] pls = end_plus - x0 pls /= np.linalg.norm(pls) end_minus = md_init_minus.coords[-1] minus = end_minus - x0 minus /= np.linalg.norm(minus) p = trans_vec @ pls m = trans_vec @ minus init_trajs_converged = np.sign(p) != np.sign(m) if init_trajs_converged: print("Trajectories ran into different basins. Breaking.") break if dump: dump_coords(geom.atoms, md_init_plus.coords, "mdp_ee_init_plus.trj") dump_coords(geom.atoms, md_init_minus.coords, "mdp_ee_init_minus.trj") assert init_trajs_converged print(f"Ran 2*{i+1} initialization trajectories.") print() # Run actual trajectories, using the supplied termination functions if possible. print(highlight_text("Running actual full trajectories.", level=1)) def print_status(terminated, step): if terminated: msg = f"\tTerminated by '{terminated}' in step {step}." else: msg = "\tMax time steps reached!" print(msg) # "Production"/Final MDs md_fin_kwargs = { "v0": v0.copy(), "steps": steps, "dt": dt, "term_funcs": term_funcs, "external": external_md, } # MD with positive v0. geom.coords = x0.copy() md_fin_plus = run_md(geom, **md_fin_kwargs) print_status(md_fin_plus.terminated, md_fin_plus.step) # MD with negative v0. geom.coords = x0.copy() md_fin_kwargs["v0"] = -v0 md_fin_minus = run_md(geom, **md_fin_kwargs) print_status(md_fin_minus.terminated, md_fin_minus.step) md_fin_plus_term = md_fin_plus.terminated md_fin_minus_term = md_fin_minus.terminated if dump: dump_coords(geom.atoms, md_fin_plus.coords, "mdp_ee_fin_plus.trj") dump_coords(geom.atoms, md_fin_minus.coords, "mdp_ee_fin_minus.trj") mdp_result = MDPResult( ascent_xs=ascent_xs, md_init_plus=md_init_plus, md_init_minus=md_init_minus, md_fin_plus=md_fin_plus, md_fin_minus=md_fin_minus, md_fin_plus_term=md_fin_plus_term, md_fin_minus_term=md_fin_minus_term, ) return mdp_result