Exemplo n.º 1
0
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)
Exemplo n.º 2
0
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")
Exemplo n.º 3
0
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)
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
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)
Exemplo n.º 6
0
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