Example #1
0
def simulate_npt_traj(thermostat: UnadjustedLangevinMove,
                      barostat: MonteCarloBarostat,
                      initial_state: CoordsVelBox,
                      n_moves=1000) -> Tuple[List[CoordsVelBox], Dict]:
    barostat.reset()

    # alternate between thermostat moves and barostat moves
    traj = [initial_state]
    volume_traj = [compute_box_volume(traj[0].box)]
    proposal_scale_traj = [barostat.max_delta_volume]

    compound_move = CompoundMove([thermostat, barostat])

    for _ in range(n_moves):
        traj.append(compound_move.move(traj[-1]))

        # accumulate result trajectories
        volume_traj.append(compute_box_volume(traj[-1].box))
        proposal_scale_traj.append(barostat.max_delta_volume)

    extras = dict(
        volume_traj=np.array(volume_traj),
        proposal_scale_traj=np.array(proposal_scale_traj),
    )

    return traj, extras
Example #2
0
    def propose(self, x: CoordsVelBox) -> Tuple[CoordsVelBox, float]:
        u_0 = self.reduced_potential_fxn(x.coords, x.box)
        volume = compute_box_volume(x.box)

        # sample uniformly in [-max_delta_volume, +max_delta_volume]
        delta_volume = (onp.random.rand() * 2 - 1) * self.max_delta_volume

        # apply scaling move
        # eq. 4 from Aqvist et al 2004
        proposed_volume = volume + delta_volume
        length_scale = (proposed_volume / volume)**(1.0 / 3)

        proposed_coords = self.centroid_rescaler.scale_centroids(
            x.coords, compute_box_center(x.box), length_scale)

        proposed_box = length_scale * x.box

        proposed_state = CoordsVelBox(proposed_coords, x.velocities,
                                      proposed_box)

        u_proposed = self.reduced_potential_fxn(proposed_coords, proposed_box)
        delta_u = u_proposed - u_0

        jacobian_contribution = self.N * jnp.log(proposed_volume / volume)

        log_acceptance_probability = jnp.minimum(
            0, -(delta_u - jacobian_contribution))

        return proposed_state, log_acceptance_probability
Example #3
0
    def reduced_potential_and_gradient(self, x, box, lam):
        U, dU_dx = self.potential_energy.energy_and_gradient(x, box, lam)
        volume = compute_box_volume(box)

        # reduced potential u
        #   (unitless)
        u = self.reduce(U, volume)

        # d reduced potential / dx
        #   (units of 1 / nm, but returned without units, since (x, box) don't have units either)
        du_dx = self.reduce(dU_dx, volume)

        return u, du_dx
Example #4
0
def test_molecular_ideal_gas():
    """


    References
    ----------
    OpenMM testIdealGas
    https://github.com/openmm/openmm/blob/d8ef57fed6554ec95684e53768188e1f666405c9/tests/TestMonteCarloBarostat.h#L86-L140
    """

    # simulation parameters
    initial_waterbox_width = 3.0 * unit.nanometer
    timestep = 1.5 * unit.femtosecond
    collision_rate = 1.0 / unit.picosecond
    n_moves = 10000
    barostat_interval = 5
    seed = 2021

    # thermodynamic parameters
    temperatures = np.array([300, 600, 1000]) * unit.kelvin
    pressure = 100.0 * unit.bar  # very high pressure, to keep the expected volume small

    # generate an alchemical system of a waterbox + alchemical ligand:
    # effectively discard ligands by running in AbsoluteFreeEnergy mode at lambda = 1.0
    mol_a = hif2a_ligand_pair.mol_a
    ff = hif2a_ligand_pair.ff
    complex_system, complex_coords, complex_box, complex_top = build_water_system(
        initial_waterbox_width.value_in_unit(unit.nanometer))

    min_complex_coords = minimize_host_4d([mol_a], complex_system,
                                          complex_coords, ff, complex_box)
    afe = AbsoluteFreeEnergy(mol_a, ff)

    _unbound_potentials, _sys_params, masses, coords = afe.prepare_host_edge(
        ff.get_ordered_params(), complex_system, min_complex_coords)

    # drop the nonbonded potential
    unbound_potentials = _unbound_potentials[:-1]
    sys_params = _sys_params[:-1]

    # get list of molecules for barostat by looking at bond table
    harmonic_bond_potential = unbound_potentials[0]
    bond_list = get_bond_list(harmonic_bond_potential)
    group_indices = get_group_indices(bond_list)

    volume_trajs = []

    relative_tolerance = 1e-2
    initial_relative_box_perturbation = 2 * relative_tolerance

    n_molecules = complex_top.getNumResidues()

    bound_potentials = []
    for params, unbound_pot in zip(sys_params, unbound_potentials):
        bp = unbound_pot.bind(np.asarray(params))
        bound_potentials.append(bp)

    u_impls = []
    for bp in bound_potentials:
        bp_impl = bp.bound_impl(precision=np.float32)
        u_impls.append(bp_impl)

    # expected volume
    md_pressure_unit = ENERGY_UNIT / DISTANCE_UNIT**3
    pressure_in_md = (
        pressure * unit.AVOGADRO_CONSTANT_NA).value_in_unit(md_pressure_unit)
    expected_volume_in_md = (n_molecules +
                             1) * BOLTZ * temperatures.value_in_unit(
                                 unit.kelvin) / pressure_in_md

    for i, temperature in enumerate(temperatures):

        # define a thermostat
        integrator = LangevinIntegrator(
            temperature.value_in_unit(unit.kelvin),
            timestep.value_in_unit(unit.picosecond),
            collision_rate.value_in_unit(unit.picosecond**-1),
            masses,
            seed,
        )
        integrator_impl = integrator.impl()

        v_0 = sample_velocities(masses * unit.amu, temperature)

        # rescale the box to be approximately the desired box volume already
        rescaler = CentroidRescaler(group_indices)
        initial_volume = compute_box_volume(complex_box)
        initial_center = compute_box_center(complex_box)
        length_scale = ((1 + initial_relative_box_perturbation) *
                        expected_volume_in_md[i] / initial_volume)**(1.0 / 3)
        new_coords = rescaler.scale_centroids(coords, initial_center,
                                              length_scale)
        new_box = complex_box * length_scale

        baro = custom_ops.MonteCarloBarostat(
            new_coords.shape[0],
            pressure.value_in_unit(unit.bar),
            temperature.value_in_unit(unit.kelvin),
            group_indices,
            barostat_interval,
            u_impls,
            seed,
        )

        ctxt = custom_ops.Context(new_coords,
                                  v_0,
                                  new_box,
                                  integrator_impl,
                                  u_impls,
                                  barostat=baro)
        vols = []
        for move in range(n_moves // barostat_interval):
            ctxt.multiple_steps(np.ones(barostat_interval))
            new_box = ctxt.get_box()
            volume = np.linalg.det(new_box)
            vols.append(volume)
        volume_trajs.append(vols)

    equil_time = len(volume_trajs[0]) // 2  # TODO: don't hard-code this?
    actual_volume_in_md = np.array(
        [np.mean(volume_traj[equil_time:]) for volume_traj in volume_trajs])

    np.testing.assert_allclose(actual=actual_volume_in_md,
                               desired=expected_volume_in_md,
                               rtol=relative_tolerance)
Example #5
0
def test_barostat_varying_pressure():
    temperature = 300.0 * unit.kelvin
    initial_waterbox_width = 3.0 * unit.nanometer
    timestep = 1.5 * unit.femtosecond
    barostat_interval = 3
    collision_rate = 1.0 / unit.picosecond
    seed = 2021
    np.random.seed(seed)

    # Start out with a very large pressure
    pressure = 1000.0 * unit.atmosphere
    mol_a = hif2a_ligand_pair.mol_a
    ff = hif2a_ligand_pair.ff
    complex_system, complex_coords, complex_box, complex_top = build_water_system(
        initial_waterbox_width.value_in_unit(unit.nanometer))

    min_complex_coords = minimize_host_4d([mol_a], complex_system,
                                          complex_coords, ff, complex_box)
    afe = AbsoluteFreeEnergy(mol_a, ff)

    unbound_potentials, sys_params, masses, coords = afe.prepare_host_edge(
        ff.get_ordered_params(), complex_system, min_complex_coords)

    # get list of molecules for barostat by looking at bond table
    harmonic_bond_potential = unbound_potentials[0]
    bond_list = get_bond_list(harmonic_bond_potential)
    group_indices = get_group_indices(bond_list)

    lam = 1.0

    u_impls = []
    for params, unbound_pot in zip(sys_params, unbound_potentials):
        bp = unbound_pot.bind(np.asarray(params))
        bp_impl = bp.bound_impl(precision=np.float32)
        u_impls.append(bp_impl)

    integrator = LangevinIntegrator(
        temperature.value_in_unit(unit.kelvin),
        timestep.value_in_unit(unit.picosecond),
        collision_rate.value_in_unit(unit.picosecond**-1),
        masses,
        seed,
    )
    integrator_impl = integrator.impl()

    v_0 = sample_velocities(masses * unit.amu, temperature)

    baro = custom_ops.MonteCarloBarostat(
        coords.shape[0],
        pressure.value_in_unit(unit.bar),
        temperature.value_in_unit(unit.kelvin),
        group_indices,
        barostat_interval,
        u_impls,
        seed,
    )

    ctxt = custom_ops.Context(coords,
                              v_0,
                              complex_box,
                              integrator_impl,
                              u_impls,
                              barostat=baro)
    ctxt.multiple_steps(np.ones(1000) * lam)
    ten_atm_box = ctxt.get_box()
    ten_atm_box_vol = compute_box_volume(ten_atm_box)
    # Expect the box to shrink thanks to the barostat
    assert compute_box_volume(complex_box) - ten_atm_box_vol > 0.4

    # Set the pressure to 1 bar
    baro.set_pressure((1 * unit.atmosphere).value_in_unit(unit.bar))
    # Changing the barostat interval resets the barostat step.
    baro.set_interval(2)

    ctxt.multiple_steps(np.ones(2000) * lam)
    atm_box = ctxt.get_box()
    # Box will grow thanks to the lower pressure
    assert compute_box_volume(atm_box) > ten_atm_box_vol
Example #6
0
def test_barostat_is_deterministic():
    """Verify that the barostat results in the same box size shift after 1000
    steps. This is important to debugging as well as providing the ability to replicate
    simulations
    """
    platform_version = get_platform_version()
    lam = 1.0
    temperature = 300.0 * unit.kelvin
    initial_waterbox_width = 3.0 * unit.nanometer
    timestep = 1.5 * unit.femtosecond
    barostat_interval = 3
    collision_rate = 1.0 / unit.picosecond
    seed = 2021
    np.random.seed(seed)

    # OpenEye's AM1 Charging values are OS platform dependent. To ensure that we have deterministic values
    # we check against our two most common OS versions, Ubuntu 18.04 and 20.04.
    box_vol = 26.869380588831582
    lig_charge_vals = np.array([
        1.4572377542719206, -0.37011462071257184, 1.1478267014520305,
        -4.920284514559682, 0.16985194917937935
    ])
    if "ubuntu" not in platform_version:
        print(
            f"Test expected to run under ubuntu 20.04 or 18.04, got {platform_version}"
        )
    if "18.04" in platform_version:
        box_vol = 26.711716908713402
        lig_charge_vals[3] = -4.920166483601927

    pressure = 1.0 * unit.atmosphere

    mol_a = hif2a_ligand_pair.mol_a
    ff = hif2a_ligand_pair.ff
    complex_system, complex_coords, complex_box, complex_top = build_water_system(
        initial_waterbox_width.value_in_unit(unit.nanometer))

    min_complex_coords = minimize_host_4d([mol_a], complex_system,
                                          complex_coords, ff, complex_box)
    afe = AbsoluteFreeEnergy(mol_a, ff)

    unbound_potentials, sys_params, masses, coords = afe.prepare_host_edge(
        ff.get_ordered_params(), complex_system, min_complex_coords)

    # get list of molecules for barostat by looking at bond table
    harmonic_bond_potential = unbound_potentials[0]
    bond_list = get_bond_list(harmonic_bond_potential)
    group_indices = get_group_indices(bond_list)

    u_impls = []
    # Look at the first five atoms and their assigned charges
    ligand_charges = sys_params[-1][:, 0][len(min_complex_coords):][:5]
    np.testing.assert_array_almost_equal(lig_charge_vals,
                                         ligand_charges,
                                         decimal=5)
    for params, unbound_pot in zip(sys_params, unbound_potentials):
        bp = unbound_pot.bind(np.asarray(params))
        bp_impl = bp.bound_impl(precision=np.float32)
        u_impls.append(bp_impl)

    integrator = LangevinIntegrator(
        temperature.value_in_unit(unit.kelvin),
        timestep.value_in_unit(unit.picosecond),
        collision_rate.value_in_unit(unit.picosecond**-1),
        masses,
        seed,
    )
    integrator_impl = integrator.impl()

    v_0 = sample_velocities(masses * unit.amu, temperature)

    baro = custom_ops.MonteCarloBarostat(
        coords.shape[0],
        pressure.value_in_unit(unit.bar),
        temperature.value_in_unit(unit.kelvin),
        group_indices,
        barostat_interval,
        u_impls,
        seed,
    )

    ctxt = custom_ops.Context(coords,
                              v_0,
                              complex_box,
                              integrator_impl,
                              u_impls,
                              barostat=baro)
    ctxt.multiple_steps(np.ones(1000) * lam)
    atm_box = ctxt.get_box()
    np.testing.assert_almost_equal(compute_box_volume(atm_box),
                                   box_vol,
                                   decimal=5)