def test_kinetic_energy_from_velocities(): atoms = 5 masses = np.ones(atoms) velocities = np.ones((atoms, 3)) E_kin = kinetic_energy_from_velocities(masses, velocities) assert E_kin == pytest.approx(1.5 * atoms * VELO2E)
def test_scale_velocities_to_temperatue(): atoms = 5 masses = np.ones(atoms) T = 298.15 E_ref = kinetic_energy_for_temperature(atoms, T) # in Bohr v_unscaled = 2 * (np.random.rand(atoms, 3) - 0.5) v_scaled = scale_velocities_to_temperatue(masses, v_unscaled, T) E_scaled = kinetic_energy_from_velocities(masses, v_scaled) assert E_scaled == pytest.approx(E_ref)
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 md(geom, v0, steps, dt, remove_com_v=True, thermostat=None, T=298.15, timecon=100, term_funcs=None, constraints=None, constraint_kwargs=None, verbose=True): """Velocity verlet integrator. Parameters ---------- geom : Geometry The system for which the dynamics are to be run. v0 : np.array, floats Initial velocities in Bohr/fs. steps : float Number of simulation steps. dt : float Timestep in fs. remove_com_v : bool, default=True Remove center-of-mass velocity. thermostat : str, optional, default None Which and whether to use a thermostat. T : float, optional, default=None Desired temperature in thermostated runs. timecon : float Timeconsanst of the thermostat in fs. term_funcs : dict, optional Iterable of functions that are called with the atomic coordinates in every MD cycle and result in termination constraints : 2d iterable, optional 2D iterable containing atom indices describing constrained bond lengths. of the MD integration when they evaluate to true. constraint_kwargs : dict, optional Keyword arguments for the constraint algorithm. verbose : bool, default=True Do additional printing when True. """ assert geom.coord_type == "cart" if term_funcs is None: term_funcs = dict() if verbose: t_ps = steps * dt * 1e-3 # Total simulation time print( f"Doing {steps} steps of {dt:.4f} fs for a total of {t_ps:.2f} ps." ) energy_forces_getter = energy_forces_getter_closure(geom) if constraint_kwargs is None: constraint_kwargs = dict() if remove_com_v and (not thermostat): print( "Center of mass velocity removal requested, but thermostat is disabled. " "Disabling velocity removal.") remove_com_v = False # Fixed degrees of freedom fixed_dof = 0 if remove_com_v: fixed_dof += 3 constrained_md = constraints is not None # Get RATTLE function from closure for constrained MD if constrained_md: fixed_dof += len(constraints) rattle = rattle_closure(geom, constraints, dt, energy_forces_getter=energy_forces_getter, **constraint_kwargs) if thermostat is not None: thermo_func = THERMOSTATS[thermostat] tau_t = dt / timecon sigma = kinetic_energy_for_temperature(len(geom.atoms), T, fixed_dof=fixed_dof) # In amu masses = geom.masses masses_rep = geom.masses_rep total_mass = masses.sum() x = geom.cart_coords # v is given in Bohr/fs v = v0 a_prev = np.zeros_like(x) xs = list() Ts = list() E_tots = list() E_pot, forces = energy_forces_getter(geom.coords) t_cur = 0 terminate = False terminate_key = None T_avg = 0 log(logger, f"Running MD with Δt={dt:.2f} fs for {steps} steps.") for step in range(steps): xs.append(x.copy()) E_kin = kinetic_energy_from_velocities(masses, v.reshape(-1, 3)) T = temperature_for_kinetic_energy(len(masses), E_kin, fixed_dof=fixed_dof) T_avg += T Ts.append(T) E_tot = E_pot + E_kin E_tots.append(E_tot) status_msg = ( f"Step {step:05d} {t_cur*1e-3: >6.2f} ps E={E_tot: >8.6f} E_h " f"T={T: >8.2f} K <T>={T_avg/(step+1): >8.2f}") if (step % 25) == 0: log(logger, status_msg) if verbose: print(status_msg) if thermostat: E_kin_new = thermo_func(E_kin, sigma, v.size - fixed_dof, tau_t) scale = (E_kin_new / E_kin)**0.5 v *= scale # RATTLE algorithm if constrained_md: x, v, E_pot, forces = rattle(x, v, forces) # Simple Velocity-Verlet integration else: E_pot, forces = energy_forces_getter(geom.coords) # Acceleration, convert from Hartree / (Bohr * amu) to Bohr/fs² a = forces / masses_rep * FORCE2ACC v += .5 * (a + a_prev) * dt if remove_com_v: v -= v * masses_rep / total_mass # v*dt = Bohr/fs * fs -> Bohr # a*dt**2 = Bohr/fs² * fs² -> Bohr x += v * dt + .5 * a * dt**2 a_prev = a # Update coordinates geom.coords = x for name, func in term_funcs.items(): if func(x.reshape(-1, 3)): terminate = True terminate_key = name break if terminate: log(logger, f"Termination function '{name}' evaluted to True. Breaking.") break if check_for_stop_sign(): break # Advance time t_cur += dt log(logger, "") md_result = MDResult( coords=np.array(xs), t_ps=t_cur * 1e-3, step=step, terminated=terminate_key, T=np.array(Ts), E_tot=np.array(E_tots), ) return md_result