Ejemplo n.º 1
0
    def test_setting(self, ethane_system_topology):
        nb_force = openmm.NonbondedForce()
        nb_force.addParticle(1, 2, 3)
        my_ommp = Ommperator(ethane_system_topology[0], ethane_system_topology[1])
        my_nb_ommp = NonbondedForceOmmperator(my_ommp, nb_force, 0)

        my_nb_ommp.charge = 10*unit.elementary_charge
        my_nb_ommp.sigma = 20*unit.nanometer
        my_nb_ommp.epsilon = 30*unit.kilojoule_per_mole

        assert my_nb_ommp.charge == nb_force.getParticleParameters(0)[0]
        assert my_nb_ommp.sigma == nb_force.getParticleParameters(0)[1]
        assert my_nb_ommp.epsilon == nb_force.getParticleParameters(0)[2]

        assert is_close(my_nb_ommp.charge, 10*unit.elementary_charge)
        assert is_close(my_nb_ommp.sigma, 20*unit.nanometer)
        assert is_close(my_nb_ommp.epsilon, 30*unit.kilojoule_per_mole)

        my_nb_ommp.set_params(charge=100*unit.elementary_charge,
                                sigma=200*unit.nanometer,
                                epsilon=300*unit.kilojoule_per_mole)

        assert my_nb_ommp.charge == nb_force.getParticleParameters(0)[0]
        assert my_nb_ommp.sigma == nb_force.getParticleParameters(0)[1]
        assert my_nb_ommp.epsilon == nb_force.getParticleParameters(0)[2]

        assert is_close(my_nb_ommp.charge, 100*unit.elementary_charge)
        assert is_close(my_nb_ommp.sigma, 200*unit.nanometer)
        assert is_close(my_nb_ommp.epsilon, 300*unit.kilojoule_per_mole)
Ejemplo n.º 2
0
    def calc_energy_forces_with_omm(self, ewald=True, short=True, long=True):
        from simtk import openmm as mm, unit
        system = mm.System()
        system.setDefaultPeriodicBoxVectors(*np.diag(self.box))
        nbforce = mm.NonbondedForce()
        system.addForce(nbforce)

        if ewald:
            nbforce.setNonbondedMethod(mm.NonbondedForce.Ewald)
        else:
            nbforce.setNonbondedMethod(mm.NonbondedForce.NoCutoff)
        nbforce.setCutoffDistance(self.cutoff)
        nbforce.setForceGroup(1)
        nbforce.setReciprocalSpaceForceGroup(2)

        for i in range(self.n_atom):
            system.addParticle(0)
            nbforce.addParticle(self.charges[i], 1.0, 0)

        integrator = mm.VerletIntegrator(0.001)
        platform = mm.Platform.getPlatformByName('Reference')
        context = mm.Context(system, integrator, platform)
        context.setPositions(self.positions)
        groups = set()
        if short: groups.add(1)
        if long: groups.add(2)
        state = context.getState(getEnergy=True, getForces=True, groups=groups)
        energy = state.getPotentialEnergy().value_in_unit(
            unit.kilojoule_per_mole)
        forces = state.getForces(asNumpy=True).value_in_unit(
            unit.kilojoule_per_mole / unit.nanometer)
        return energy, forces
Ejemplo n.º 3
0
    def _create_empty_system(cutoff):
        """Creates an empty system object with stub forces.

        Parameters
        ----------
        cutoff: simtk.unit
            The non-bonded cutoff.

        Returns
        -------
        simtk.openmm.System
            The created system object.
        """

        system = openmm.System()

        system.addForce(openmm.HarmonicBondForce())
        system.addForce(openmm.HarmonicAngleForce())
        system.addForce(openmm.PeriodicTorsionForce())

        nonbonded_force = openmm.NonbondedForce()
        nonbonded_force.setCutoffDistance(cutoff)
        nonbonded_force.setNonbondedMethod(openmm.NonbondedForce.PME)

        system.addForce(nonbonded_force)

        return system
Ejemplo n.º 4
0
    def _add_nonbonded_force_terms(self):
        standard_nonbonded_force = openmm.NonbondedForce()
        self._out_system.addForce(standard_nonbonded_force)
        self._out_system_forces[standard_nonbonded_force.__class__.
                                __name__] = standard_nonbonded_force

        #set the appropriate parameters
        epsilon_solvent = self._og_system_forces[
            'NonbondedForce'].getReactionFieldDielectric()
        r_cutoff = self._og_system_forces['NonbondedForce'].getCutoffDistance()
        switch_bool = self._og_system_forces[
            'NonbondedForce'].getUseSwitchingFunction()
        standard_nonbonded_force.setUseSwitchingFunction(switch_bool)
        if switch_bool:
            switching_distance = self._og_system_forces[
                'NonbondedForce'].getSwitchingDistance()
            standard_nonbonded_force.setSwitchingDistance(switching_distance)

        if self._nonbonded_method != openmm.NonbondedForce.NoCutoff:
            standard_nonbonded_force.setReactionFieldDielectric(
                epsilon_solvent)
            standard_nonbonded_force.setCutoffDistance(r_cutoff)
        if self._nonbonded_method in [
                openmm.NonbondedForce.PME, openmm.NonbondedForce.Ewald
        ]:
            [alpha_ewald, nx, ny,
             nz] = self._og_system_forces['NonbondedForce'].getPMEParameters()
            delta = self._og_system_forces[
                'NonbondedForce'].getEwaldErrorTolerance()
            standard_nonbonded_force.setPMEParameters(alpha_ewald, nx, ny, nz)
            standard_nonbonded_force.setEwaldErrorTolerance(delta)
        standard_nonbonded_force.setNonbondedMethod(self._nonbonded_method)

        if self._og_system_forces['NonbondedForce'].getUseDispersionCorrection(
        ) and self._use_dispersion_correction:
            self._out_system_forces[
                'NonbondedForce'].setUseDispersionCorrection(True)
        else:
            self._out_system_forces[
                'NonbondedForce'].setUseDispersionCorrection(False)

        #add the global value
        self._out_system_forces['NonbondedForce'].addGlobalParameter(
            'electrostatic_scale', 0.)
        self._out_system_forces['NonbondedForce'].addGlobalParameter(
            'steric_scale', 0.)
Ejemplo n.º 5
0
    def _addNonbondedForceToSystem(self, sys):
        """Create the nonbonded force
        """
        nb = mm.NonbondedForce()
        sys.addForce(nb)

        q = """SELECT charge, sigma, epsilon
        FROM particle INNER JOIN nonbonded_param
        ON particle.nbtype=nonbonded_param.id"""
        for charge, sigma, epsilon in self._conn.execute(q):
            nb.addParticle(charge, sigma * angstrom,
                           epsilon * kilocalorie_per_mole)

        for p0, p1 in self._conn.execute('SELECT p0, p1 FROM exclusion'):
            nb.addException(p0, p1, 0.0, 1.0, 0.0)

        q = """SELECT p0, p1, aij, bij, qij
        FROM pair_12_6_es_term INNER JOIN pair_12_6_es_param
        ON pair_12_6_es_term.param=pair_12_6_es_param.id;"""
        for p0, p1, a_ij, b_ij, q_ij in self._conn.execute(q):
            a_ij = (a_ij * kilocalorie_per_mole * (angstrom**12)).in_units_of(
                kilojoule_per_mole * (nanometer**12))
            b_ij = (b_ij * kilocalorie_per_mole * (angstrom**6)).in_units_of(
                kilojoule_per_mole * (nanometer**6))
            q_ij = q_ij * elementary_charge**2

            if (b_ij._value == 0.0) or (a_ij._value == 0.0):
                new_epsilon = 0
                new_sigma = 1
            else:
                new_epsilon = b_ij**2 / (4 * a_ij)
                new_sigma = (a_ij / b_ij)**(1.0 / 6.0)
            nb.addException(p0, p1, q_ij, new_sigma, new_epsilon, True)

        n_total = self._conn.execute(
            """SELECT COUNT(*) FROM pair_12_6_es_term""").fetchone()
        n_in_exclusions = self._conn.execute("""SELECT COUNT(*)
        FROM exclusion INNER JOIN pair_12_6_es_term
        ON exclusion.p0==pair_12_6_es_term.p0 AND exclusion.p1==pair_12_6_es_term.p1"""
                                             ).fetchone()
        if not n_total == n_in_exclusions:
            raise NotImplementedError(
                'All pair_12_6_es_terms must have a corresponding exclusion')

        return nb
Ejemplo n.º 6
0
def perform_test(sigma, epsilon, charge0, charge1, rs, rc):
    nonbonded = openmm.NonbondedForce()
    nonbonded.addParticle(charge0, sigma, epsilon)
    nonbonded.addParticle(charge1, sigma, epsilon)
    nonbonded.setNonbondedMethod(nonbonded.CutoffNonPeriodic)
    platform = openmm.Platform.getPlatformByName('Reference')
    system = openmm.System()
    system.addParticle(1)
    system.addParticle(1)
    system.addForce(nonbonded)
    ufedmm.add_inner_nonbonded_force(system, rs, rc, 1)
    context = openmm.Context(system, openmm.CustomIntegrator(0), platform)
    ONE_4PI_EPS0 = 138.93545764438198
    for r in np.linspace(sigma, rc, 101):
        context.setPositions([[0, 0, 0], [r, 0, 0]])
        state = context.getState(getForces=True, groups={1})
        force = state.getForces()[1].x
        F = 24 * epsilon * (
            2 * (sigma / r)**12 -
            (sigma / r)**6) / r + ONE_4PI_EPS0 * charge0 * charge1 / r**2
        assert force == pytest.approx(F * S((r - rs) / (rc - rs)))
Ejemplo n.º 7
0
def test_nonbonded_kernel():
    pairs = list(itertools.combinations(list(range(top.n_atom)), 2))
    parameters = [(i, j / 1000, i / 10 + j / 10) for i, j in pairs]

    kernel = NonbondedKernel(top.positions, pairs, parameters)
    r, energy, forces = kernel.evaluate()
    print()
    print(r)
    print(energy)
    print(sum(energy))
    print(forces)

    force = mm.NonbondedForce()
    force.setNonbondedMethod(mm.NonbondedForce.NoCutoff)
    force.setForceGroup(5)
    for i in range(top.n_atom):
        force.addParticle(0, 1, 0)
    for (i, j), parameter in zip(pairs, parameters):
        force.addException(i, j, parameter[2], parameter[1], parameter[0])
    e, f = calc_energy_with_omm(force, top.positions)

    assert pytest.approx(sum(energy), rel=1E-6) == e
    assert pytest.approx(forces, rel=1E-6) == f
Ejemplo n.º 8
0
def add_force(cgmodel, force_type=None, rosetta_functional_form=False):
    """

    Given a 'cgmodel' and 'force_type' as input, this function adds
    the OpenMM force corresponding to 'force_type' to 'cgmodel.system'.

    :param cgmodel: CGModel() class object.
    :param type: class

    :param force_type: Designates the kind of 'force' provided. (Valid options include: "Bond", "Nonbonded", "Angle", and "Torsion")
    :type force_type: str

    :returns:
         - cgmodel (class) - 'foldamers' CGModel() class object
         - force (class) - An OpenMM `Force() <https://simtk.org/api_docs/openmm/api4_1/python/classsimtk_1_1openmm_1_1openmm_1_1Force.html>`_ object.

    :Example:

    >>> from foldamers.cg_model.cgmodel import CGModel
    >>> cgmodel = CGModel()
    >>> force_type = "Bond"
    >>> cgmodel,force = add_force(cgmodel,force_type=force_type)

    """
    if force_type == "Bond":

        bond_force = mm.HarmonicBondForce()
        bond_list = []

        for bond_indices in cgmodel.get_bond_list():
            bond_list.append([bond_indices[0], bond_indices[1]])
            if cgmodel.include_bond_forces:
                bond_force_constant = cgmodel.get_bond_force_constant(
                    bond_indices)
                bond_length = cgmodel.get_bond_length(bond_indices)
                bond_force.addBond(
                    bond_indices[0],
                    bond_indices[1],
                    bond_length.value_in_unit(unit.nanometer),
                    bond_force_constant.value_in_unit(unit.kilojoule_per_mole /
                                                      unit.nanometer**2),
                )
            if cgmodel.constrain_bonds:
                bond_length = cgmodel.get_bond_length(bond_indices)
                if not cgmodel.include_bond_forces:
                    bond_force.addBond(
                        bond_indices[0],
                        bond_indices[1],
                        bond_length.value_in_unit(unit.nanometer),
                        0.0,
                    )
                cgmodel.system.addConstraint(bond_indices[0], bond_indices[1],
                                             bond_length)

        if len(bond_list) != bond_force.getNumBonds():
            print(
                "ERROR: The number of bonds in the coarse grained model is different\n"
            )
            print("from the number of bonds in its OpenMM System object\n")
            print("There are " + str(len(bond_list)) +
                  " bonds in the coarse grained model\n")
            print("and " + str(bond_force.getNumBonds()) +
                  " bonds in the OpenMM system object.")
            exit()

        cgmodel.system.addForce(bond_force)
        force = bond_force

    if force_type == "Nonbonded":

        if cgmodel.binary_interaction_parameters:
            # If not an empty dictionary, use the parameters within

            for key, value in cgmodel.binary_interaction_parameters.items():
                # TODO: make kappa work for systems with more than 2 bead types
                kappa = value

            # Use custom nonbonded force with binary interaction parameter
            nonbonded_force = mm.CustomNonbondedForce(
                f"4*epsilon*((sigma/r)^12-(sigma/r)^6); sigma=0.5*(sigma1+sigma2); epsilon=(1-kappa)*sqrt(epsilon1*epsilon2)"
            )
            nonbonded_force.addPerParticleParameter("sigma")
            nonbonded_force.addPerParticleParameter("epsilon")

            # We need to specify a default value of kappa when adding global parameter
            nonbonded_force.addGlobalParameter("kappa", kappa)

            # TODO: add the rosetta_function_form switching function
            nonbonded_force.setNonbondedMethod(mm.NonbondedForce.NoCutoff)

            for particle in range(cgmodel.num_beads):
                # We don't need to define charge here, though we should add it in the future
                # We also don't need to define kappa since it is a global parameter
                sigma = cgmodel.get_particle_sigma(particle)
                epsilon = cgmodel.get_particle_epsilon(particle)
                nonbonded_force.addParticle((sigma, epsilon))

            if len(cgmodel.bond_list) >= 1:
                #***Note: customnonbonded force uses 'Exclusion' rather than 'Exception'
                # Each of these also takes different arguments
                if not rosetta_functional_form:
                    # This should not be applied if there are no angle forces.
                    if cgmodel.include_bond_angle_forces:
                        bond_cut = 2  # Particles separated by this many bonds or fewer are excluded
                        # A value of 2 means that 1-2, 1-3 interactions are 0, 1-4 interactions are 1
                        nonbonded_force.createExclusionsFromBonds(
                            cgmodel.bond_list, bond_cut)
                    else:
                        # Just remove the 1-2 nonbonded interactions.
                        # For customNonbondedForce, don't need to set charge product and epsilon here
                        for bond in cgmodel.bond_list:
                            nonbonded_force.addExclusion(bond[0], bond[1])

        else:
            nonbonded_force = mm.NonbondedForce()

            if rosetta_functional_form:
                # rosetta has a 4.5-6 A vdw cutoff.  Note the OpenMM cutoff may not be quite the same
                # functional form as the Rosetta cutoff, but it should be somewhat close.
                nonbonded_force.setNonbondedMethod(
                    mm.NonbondedForce.CutoffNonPeriodic)
                nonbonded_force.setCutoffDistance(
                    0.6)  # rosetta cutoff distance in nm
                nonbonded_force.setUseSwitchingFunction(True)
                nonbonded_force.setSwitchingDistance(
                    0.45)  # start of rosetta switching distance in nm
            else:
                nonbonded_force.setNonbondedMethod(mm.NonbondedForce.NoCutoff)

            for particle in range(cgmodel.num_beads):
                charge = cgmodel.get_particle_charge(particle)
                sigma = cgmodel.get_particle_sigma(particle)
                epsilon = cgmodel.get_particle_epsilon(particle)
                nonbonded_force.addParticle(charge, sigma, epsilon)

            if len(cgmodel.bond_list) >= 1:
                if not rosetta_functional_form:
                    # This should not be applied if there are no angle forces.
                    if cgmodel.include_bond_angle_forces:
                        nonbonded_force.createExceptionsFromBonds(
                            cgmodel.bond_list, 1.0, 1.0)
                    else:
                        # Just remove the 1-2 nonbonded interactions.
                        # If charge product and epsilon are 0, the interaction is omitted.
                        for bond in cgmodel.bond_list:
                            nonbonded_force.addException(
                                bond[0], bond[1], 0.0, 1.0, 0.0)
                if rosetta_functional_form:
                    # Remove i+3 interactions
                    nonbonded_force.createExceptionsFromBonds(
                        cgmodel.bond_list, 0.0, 0.0)
                    # Reduce the strength of i+4 interactions
                    for torsion in cgmodel.torsion_list:
                        for bond in cgmodel.bond_list:
                            if bond[0] not in torsion:
                                if bond[1] == torsion[0]:
                                    nonbonded_force = add_rosetta_exception_parameters(
                                        cgmodel, nonbonded_force, bond[0],
                                        torsion[3])
                                if bond[1] == torsion[3]:
                                    nonbonded_force = add_rosetta_exception_parameters(
                                        cgmodel, nonbonded_force, bond[0],
                                        torsion[0])
                            if bond[1] not in torsion:
                                if bond[0] == torsion[0]:
                                    nonbonded_force = add_rosetta_exception_parameters(
                                        cgmodel, nonbonded_force, bond[1],
                                        torsion[3])
                                if bond[0] == torsion[3]:
                                    nonbonded_force = add_rosetta_exception_parameters(
                                        cgmodel, nonbonded_force, bond[1],
                                        torsion[0])
        cgmodel.system.addForce(nonbonded_force)
        force = nonbonded_force

    if force_type == "Angle":
        angle_force = mm.HarmonicAngleForce()
        for angle in cgmodel.bond_angle_list:
            bond_angle_force_constant = cgmodel.get_bond_angle_force_constant(
                angle)
            equil_bond_angle = cgmodel.get_equil_bond_angle(angle)
            angle_force.addAngle(
                angle[0],
                angle[1],
                angle[2],
                equil_bond_angle.value_in_unit(unit.radian),
                bond_angle_force_constant.value_in_unit(
                    unit.kilojoule_per_mole / unit.radian**2),
            )
        cgmodel.system.addForce(angle_force)
        force = angle_force

    if force_type == "Torsion":
        torsion_force = mm.PeriodicTorsionForce()
        for torsion in cgmodel.torsion_list:
            torsion_force_constant = cgmodel.get_torsion_force_constant(
                torsion)
            torsion_phase_angle = cgmodel.get_torsion_phase_angle(torsion)
            periodicity = cgmodel.get_torsion_periodicity(torsion)

            if type(periodicity) == list:
                # Check periodic torsion parameter lists:
                # These can be either a list of quantities, or a quantity with a list as its value

                # Check torsion_phase_angle parameters:
                if type(torsion_phase_angle) == unit.quantity.Quantity:
                    # This is either a single quantity, or quantity with a list value
                    if type(torsion_phase_angle.value_in_unit(
                            unit.radian)) == list:
                        # Check if there are either 1 or len(periodicity) elements
                        if len(torsion_phase_angle) != len(
                                periodicity) and len(torsion_phase_angle) != 1:
                            # Mismatch is list lengths
                            print(
                                'ERROR: incompatible periodic torsion parameter lists'
                            )
                            exit()
                        if len(torsion_phase_angle) == 1:
                            # This happens when input is '[value]*unit.radian'
                            torsion_phase_angle_list = []
                            for i in range(len(periodicity)):
                                torsion_phase_angle_list.append(
                                    torsion_phase_angle[0])
                            # This is a list of quantities
                            torsion_phase_angle = torsion_phase_angle_list
                    else:
                        # Single quantity - apply same angle to all periodic terms:
                        torsion_phase_angle_list = []
                        for i in range(len(periodicity)):
                            torsion_phase_angle_list.append(
                                torsion_phase_angle)
                        # This is a list of quantities
                        torsion_phase_angle = torsion_phase_angle_list
                else:
                    # This is a list of quantities or incorrect input
                    if len(torsion_phase_angle) == 1:
                        # This is a list containing a single quantity
                        torsion_phase_angle_list = []
                        for i in range(len(periodicity)):
                            torsion_phase_angle_list.append(
                                torsion_phase_angle[0])
                        # This is a list of quantities
                        torsion_phase_angle = torsion_phase_angle_list

                # Check torsion_force_constant parameters:
                if type(torsion_force_constant) == unit.quantity.Quantity:
                    # This is either a single quantity, or quantity with a list value
                    if type(
                            torsion_force_constant.value_in_unit(
                                unit.kilojoule_per_mole)) == list:
                        # Check if there are either 1 or len(periodicity) elements
                        if len(torsion_force_constant) != len(
                                periodicity) and len(
                                    torsion_force_constant) != 1:
                            # Mismatch is list lengths
                            print(
                                'ERROR: incompatible periodic torsion parameter lists'
                            )
                            exit()
                        if len(torsion_force_constant) == 1:
                            # This happens when input is '[value]*unit.kilojoule_per_mole'
                            torsion_force_constant_list = []
                            for i in range(len(periodicity)):
                                torsion_force_constant_list.append(
                                    torsion_force_constant[0])
                            # This is a list of quantities
                            torsion_force_constant = torsion_force_constant_list
                    else:
                        # Single quantity - apply same angle to all periodic terms:
                        torsion_force_constant_list = []
                        for i in range(len(periodicity)):
                            torsion_force_constant_list.append(
                                torsion_force_constant)
                        # This is a list of quantities
                        torsion_force_constant = torsion_force_constant_list
                else:
                    # This is a list of quantities or incorrect input
                    if len(torsion_force_constant) == 1:
                        # This is a list containing a single quantity
                        torsion_force_constant_list = []
                        for i in range(len(periodicity)):
                            torsion_force_constant_list.append(
                                torsion_force_constant[0])
                        # This is a list of quantities
                        torsion_force_constant = torsion_force_constant_list

                # Add torsion force:
                for i in range(len(periodicity)):
                    # print(f'Adding torsion term to particles [{torsion[0]} {torsion[1]} {torsion[2]} {torsion[3]}]')
                    # print(f'periodicity: {periodicity[i]}')
                    # print(f'torsion_phase_angle: {torsion_phase_angle[i]}')
                    # print(f'torsion_force_constant: {torsion_force_constant[i]}\n')
                    torsion_force.addTorsion(
                        torsion[0],
                        torsion[1],
                        torsion[2],
                        torsion[3],
                        periodicity[i],
                        torsion_phase_angle[i].value_in_unit(unit.radian),
                        torsion_force_constant[i].value_in_unit(
                            unit.kilojoule_per_mole),
                    )

            else:
                # Single periodic torsion term:
                torsion_force.addTorsion(
                    torsion[0],
                    torsion[1],
                    torsion[2],
                    torsion[3],
                    periodicity,
                    torsion_phase_angle.value_in_unit(unit.radian),
                    torsion_force_constant.value_in_unit(
                        unit.kilojoule_per_mole),
                )

        cgmodel.system.addForce(torsion_force)

        # print(f"Number of torsion forces: {cgmodel.system.getForces()[3].getNumTorsions()}")
        force = torsion_force

    return (cgmodel, force)
Ejemplo n.º 9
0
    mass = 39.9 * units.amu
    charge = 0.05 * units.elementary_charge
    sigma = 3.350 * units.angstrom
    epsilon = 100 * 0.001603 * units.kilojoule_per_mole

    # Simulation parameters.
    temperature = 300.0 * units.kelvin
    kT = kB * temperature
    beta = 1.0 / kT

    import simtk.openmm as openmm

    # Create receptor.
    receptor_system = openmm.System()
    receptor_system.addParticle(10 * mass)
    force = openmm.NonbondedForce()
    force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff)
    charge = +charge * units.elementary_charge  # DEBUG
    force.addParticle(charge, sigma, epsilon)
    receptor_system.addForce(force)

    # Create ligand.
    ligand_system = openmm.System()
    ligand_system.addParticle(mass)
    force = openmm.NonbondedForce()
    force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff)
    charge = -charge * units.elementary_charge  # DEBUG
    force.addParticle(charge, sigma, epsilon)
    ligand_system.addForce(force)

    # Prevent receptor from diffusing away by imposing a spring.
Ejemplo n.º 10
0
             1])  # lambda starts at 0, we will increase it to 1
    elif particle_index not in [1728, 1729]:
        # Add nonbonded LJ parameters for a water
        sigma = waterbox_nbforce.getParticleParameters(particle_index)[1]
        epsilon = waterbox_nbforce.getParticleParameters(particle_index)[2]
        alchemical_nbforce.addParticle([sigma, epsilon,
                                        0])  # lambda is ALWAYS 0
system.addForce(alchemical_nbforce)
# use a switching function to smoothly truncate forces to zero from 10-12 angstrom
alchemical_nbforce.setUseSwitchingFunction(use=True)
alchemical_nbforce.setSwitchingDistance(2.0 * unit.angstrom)

# and now we're going to build out a "normal", non-alchemical NonbondedForce.
# Even though O2 will not be part of this force, any NonbondedForce object
# must contain parameters for EVERY atom in the system, even if the parameters are zero
nbforce = mm.NonbondedForce()
nbforce.setNonbondedMethod(mm.NonbondedForce.CutoffPeriodic)
nbforce.setCutoffDistance(12.0 * unit.angstrom)
nbforce.setUseSwitchingFunction(use=True)
nbforce.setSwitchingDistance(2.0 * unit.angstrom)
# use the long-range dispersion correction for isotropic fluids in NPT
# Michael R. Shirts, David L. Mobley, John D. Chodera, and Vijay S. Pande.
# Accurate and efficient corrections for missing dispersion interactions in molecular simulations.
# Journal of Physical Chemistry B, 111:13052–13063, 2007.
nbforce.setUseDispersionCorrection(True)
for particle_index in range(system.getNumParticles()):
    # set LJ parameters of each paricle
    if particle_index in [1728, 1729]:
        # O2 has 0 -- the only way it interats with water is via alchemical_nbforce
        charge = 0.0 * unit.coulomb
        sigma = 0.0 * unit.nanometer
Ejemplo n.º 11
0
def create_alchemical_intermediates(reference_system,
                                    bond_atoms,
                                    bond_lambda,
                                    kT,
                                    annihilate=False):
    """
    Build alchemically-modified system where ligand is decoupled or annihilated using Custom*Force classes.

    ARGUMENTS

    reference_system (simtk.openmm.System) - reference System object from which alchemical derivatives will be made (will not be modified)
    bond_atoms (list of int) - atoms spanning bond to be eliminated    
    bond_lambda (float) - lambda value for bond breaking (lambda = 1 is original system, lambda = 0 is broken-bond system)
    kT (simtk.unit.Quantity with units compatible with simtk.unit.kilocalories_per_mole) - thermal energy, used in constructing alchemical intermediates

    RETURNS

    system (simtk.openmm.System) - alchemical intermediate copy

    """

    # Create new system.
    system = openmm.System()

    # Set periodic box vectors.
    [a, b, c] = reference_system.getDefaultPeriodicBoxVectors()
    system.setDefaultPeriodicBoxVectors(a, b, c)

    # Add atoms.
    for atom_index in range(reference_system.getNumParticles()):
        mass = reference_system.getParticleMass(atom_index)
        system.addParticle(mass)

    # Add constraints
    for constraint_index in range(reference_system.getNumConstraints()):
        [iatom, jatom,
         r0] = reference_system.getConstraintParameters(constraint_index)
        # Raise an exception if the specified bond_atoms are part of a constrained bond; we can't handle that.
        if (iatom in bond_atoms) and (jatom in bond_atoms):
            raise Exception("Bond to be broken is part of a constraint.")
        system.addConstraint(iatom, jatom, r0)

    # Perturb force terms.
    for force_index in range(reference_system.getNumForces()):
        # Dispatch forces based on reference force type.
        reference_force = reference_system.getForce(force_index)

        if bond_lambda == 1.0:
            # Just make a copy of the force if lambda = 1.
            force = copy.deepcopy(reference_force)
            system.addForce(force)
            continue

        if isinstance(reference_force, openmm.HarmonicBondForce):
            force = openmm.HarmonicBondForce()
            for bond_index in range(reference_force.getNumBonds()):
                # Retrieve parameters.
                [iatom, jatom, r0,
                 K] = reference_force.getBondParameters(bond_index)
                if (iatom in bond_atoms) and (jatom in bond_atoms):
                    if bond_lambda == 0.0:
                        continue  # eliminate this bond if broken
                    # Replace this bond with a soft-core (Morse) bond.
                    softcore_bond_force = create_softcore_bond(
                        iatom, jatom, r0, K, kT, bond_lambda)
                    system.addForce(softcore_bond_force)
                else:
                    # Add bond parameters.
                    force.addBond(iatom, jatom, r0, K)

            # Add force to new system.
            system.addForce(force)

        elif isinstance(reference_force, openmm.HarmonicAngleForce):
            force = openmm.HarmonicAngleForce()
            for angle_index in range(reference_force.getNumAngles()):
                # Retrieve parameters.
                [iatom, jatom, katom, theta0,
                 Ktheta] = reference_force.getAngleParameters(angle_index)
                # Turn off angle terms that span bond.
                if ((iatom in bond_atoms) and
                    (jatom in bond_atoms)) or ((jatom in bond_atoms) and
                                               (katom in bond_atoms)):
                    if bond_lambda == 0.0:
                        continue  # eliminate this angle if bond broken
                    Ktheta *= bond_lambda
                # Add parameters.
                force.addAngle(iatom, jatom, katom, theta0, Ktheta)
            # Add force to system.
            system.addForce(force)

        elif isinstance(reference_force, openmm.PeriodicTorsionForce):
            force = openmm.PeriodicTorsionForce()
            for torsion_index in range(reference_force.getNumTorsions()):
                # Retrieve parmaeters.
                [
                    particle1, particle2, particle3, particle4, periodicity,
                    phase, k
                ] = reference_force.getTorsionParameters(torsion_index)
                # Annihilate if torsion spans bond.
                if ((particle1 in bond_atoms) and
                    (particle2 in bond_atoms)) or (
                        (particle2 in bond_atoms) and
                        (particle3 in bond_atoms)) or (
                            (particle3 in bond_atoms) and
                            (particle4 in bond_atoms)):
                    if bond_lambda == 0.0:
                        continue  # eliminate this torsion if bond broken
                    k *= bond_lambda
                # Add parameters.
                force.addTorsion(particle1, particle2, particle3, particle4,
                                 periodicity, phase, k)
            # Add force to system.
            system.addForce(force)

        elif isinstance(reference_force, openmm.NonbondedForce):
            # NonbondedForce will handle charges and exception interactions.
            force = openmm.NonbondedForce()
            for particle_index in range(reference_force.getNumParticles()):
                # Retrieve parameters.
                [charge, sigma, epsilon
                 ] = reference_force.getParticleParameters(particle_index)
                # Lennard-Jones and electrostatic interactions involving atoms in bond will be handled by CustomNonbondedForce except at lambda = 0 or 1.
                if ((bond_lambda > 0) and
                    (bond_lambda < 1)) and (particle_index in bond_atoms):
                    # TODO: We have to also add softcore electrostatics.
                    epsilon *= 0.0
                # Add modified particle parameters.
                force.addParticle(charge, sigma, epsilon)
            for exception_index in range(reference_force.getNumExceptions()):
                # Retrieve parameters.
                [iatom, jatom, chargeprod, sigma, epsilon
                 ] = reference_force.getExceptionParameters(exception_index)
                # Modify exception for bond atoms.
                if ((iatom in bond_atoms) and (jatom in bond_atoms)):
                    if (bond_lambda == 0.0):
                        continue  # Omit exception if bond has been turned off.
                    # Alchemically modify epsilon and chargeprod.
                    if (iatom in bond_atoms) and (jatom in bond_atoms):
                        # Attenuate exception interaction (since it will be covered by CustomNonbondedForce interactions).
                        epsilon *= bond_lambda
                        chargeprod *= bond_lambda
                    # TODO: Compute restored (1,3) and (1,4) interactions across modified bond.
                # Add modified exception parameters.
                force.addException(iatom, jatom, chargeprod, sigma, epsilon)
            # Set parameters.
            force.setNonbondedMethod(reference_force.getNonbondedMethod())
            force.setCutoffDistance(reference_force.getCutoffDistance())
            force.setReactionFieldDielectric(
                reference_force.getReactionFieldDielectric())
            force.setEwaldErrorTolerance(
                reference_force.getEwaldErrorTolerance())
            # Add force to new system.
            system.addForce(force)

            if (bond_lambda == 0.0) or (bond_lambda == 1.0):
                continue  # don't need softcore if bond is turned off

            # CustomNonbondedForce will handle the softcore interactions with and among alchemically-modified atoms.
            # Softcore potential.
            # TODO: Add coulomb interaction.
            energy_expression = "4*epsilon*compute*x*(x-1.0);"
            energy_expression += "x = 1.0/(alpha*(bond_lambda*(1.0-bond_lambda)/0.25) + (r/sigma)^6);"
            energy_expression += "epsilon = sqrt(epsilon1*epsilon2);"
            energy_expression += "sigma = 0.5*(sigma1 + sigma2);"
            energy_expression += "compute = (1-bond_lambda)*alchemical1*alchemical2 + (alchemical1*(1-alchemical2) + (1-alchemical1)*alchemical2);"  # only compute interactions with or between alchemically-modified atoms

            force = openmm.CustomNonbondedForce(energy_expression)
            alpha = 0.5  # softcore parameter
            force.addGlobalParameter("alpha", alpha)
            force.addGlobalParameter("bond_lambda", bond_lambda)
            force.addPerParticleParameter("charge")
            force.addPerParticleParameter("sigma")
            force.addPerParticleParameter("epsilon")
            force.addPerParticleParameter("alchemical")
            for particle_index in range(reference_force.getNumParticles()):
                # Retrieve parameters.
                [charge, sigma, epsilon
                 ] = reference_force.getParticleParameters(particle_index)
                # Alchemically modify parameters.
                if particle_index in bond_atoms:
                    force.addParticle([charge, sigma, epsilon, 1])
                else:
                    force.addParticle([charge, sigma, epsilon, 0])
            for exception_index in range(reference_force.getNumExceptions()):
                # Retrieve parameters.
                [iatom, jatom, chargeprod, sigma, epsilon
                 ] = reference_force.getExceptionParameters(exception_index)
                # Exclude exception for bonded atoms.
                if (iatom in bond_atoms) and (jatom in bond_atoms): continue
                # All exceptions are handled by NonbondedForce, so we exclude all these here.
                force.addExclusion(iatom, jatom)
            if reference_force.getNonbondedMethod() in [
                    openmm.NonbondedForce.Ewald, openmm.NonbondedForce.PME
            ]:
                force.setNonbondedMethod(
                    openmm.CustomNonbondedForce.CutoffPeriodic)
            else:
                force.setNonbondedMethod(reference_force.getNonbondedMethod())
            force.setCutoffDistance(reference_force.getCutoffDistance())
            system.addForce(force)

        else:
            # Add copy of force term.
            force = copy.deepcopy(reference_force)
            system.addForce(force)

    return system
Ejemplo n.º 12
0
def distributeAtoms(boxsize=[34, 34, 34],
                     nparticles=1000,
                     reduced_density=0.05,
                     mass=39.9 * unit.amu,  # argon
                     sigma=3.4 * unit.angstrom,  # argon,
                     epsilon=0.238 * unit.kilocalories_per_mole,  # argon,
                     cutoff=None,
                     switch_width=3.4 * unit.angstrom,  # argon
                     dispersion_correction=True,
                     lattice=False,
                     charge=None,
                     **kwargs):
        # Determine Lennard-Jones cutoff.
        if cutoff is None:
            cutoff = 3.0 * sigma

        charge = 0.0 * unit.elementary_charge
        cutoff_type = openmm.NonbondedForce.CutoffPeriodic

        # Create an empty system object.
        system = openmm.System()

        # Periodic box vectors.
        a = unit.Quantity((boxsize[0] * unit.angstrom, 0 * unit.angstrom, 0 * unit.angstrom))
        b = unit.Quantity((0 * unit.angstrom, boxsize[1] * unit.angstrom, 0 * unit.angstrom))
        c = unit.Quantity((0 * unit.angstrom, 0 * unit.angstrom, boxsize[2] * unit.angstrom))
        system.setDefaultPeriodicBoxVectors(a, b, c)

        # Set up periodic nonbonded interactions with a cutoff.
        nb = openmm.NonbondedForce()
        nb.setNonbondedMethod(cutoff_type)
        nb.setCutoffDistance(cutoff)
        nb.setUseDispersionCorrection(dispersion_correction)

        nb.setUseSwitchingFunction(False)
        if (switch_width is not None):
            nb.setUseSwitchingFunction(True)
            nb.setSwitchingDistance(cutoff - switch_width)

        for particle_index in range(nparticles):
            system.addParticle(mass)
            nb.addParticle(charge, sigma, epsilon)

        positions = subrandom_particle_positions(nparticles, system.getDefaultPeriodicBoxVectors(), 2)
        # Add the nonbonded force.
        system.addForce(nb)

        # Add a restrining potential to keep atoms in z=0
        energy_expression = 'k * (z^2)'
        force = openmm.CustomExternalForce(energy_expression)
        force.addGlobalParameter('k', 100)
        for particle_index in range(nparticles):
            force.addParticle(particle_index, [])
        system.addForce(force)

        # Create topology.
        topology = app.Topology()
        element = app.Element.getBySymbol('Ar')
        chain = topology.addChain()
        for particle in range(system.getNumParticles()):
            residue = topology.addResidue('Ar', chain)
            topology.addAtom('Ar', element, residue)
        topology.setUnitCellDimensions(unit.Quantity(boxsize, unit.angstrom)) 
            
        # Simulate it
        from simtk.openmm import LangevinIntegrator, VerletIntegrator
        from simtk.openmm.app import Simulation, PDBReporter, StateDataReporter, PDBFile
        from simtk.unit import kelvin, picoseconds, picosecond, angstrom
        from sys import stdout
        from mdtraj.reporters import DCDReporter
        #from dcdreporter import DCDReporter
        nsteps = 10000
        freq = 1
        #integrator = LangevinIntegrator(300 * kelvin, 1 / picosecond, 0.002 * picoseconds)
        integrator = VerletIntegrator(0.002 * picoseconds)
        simulation = Simulation(topology, system, integrator)
        simulation.context.setPositions(positions)
        simulation.minimizeEnergy()
        simulation.reporters.append(DCDReporter('output.dcd', 1))
        simulation.reporters.append(StateDataReporter(stdout, 1000, potentialEnergy=True, totalEnergy=True, step=True, separator='   '))
        simulation.step(nsteps)

        state = simulation.context.getState(getPositions=True)
        finalpos = state.getPositions(asNumpy=True).value_in_unit(angstrom)

        with open('topology.pdb', 'w') as f:
            PDBFile.writeFile(topology, positions, f)

        from htmd.molecule.molecule import Molecule
        mol = Molecule('topology.pdb')
        mol.read('output.dcd')

        return finalpos, mol, system, simulation
Ejemplo n.º 13
0
def _process_nonbonded_forces(openff_sys, openmm_sys):
    """Process the vdW and Electrostatics sections of an OpenFF System into a corresponding openmm.NonbondedForce"""
    # Store the pairings, not just the supported methods for each
    supported_cutoff_methods = [["cutoff", "pme"]]

    vdw_handler = openff_sys.handlers["vdW"]
    if vdw_handler.method not in [val[0] for val in supported_cutoff_methods]:
        raise UnsupportedCutoffMethodError()

    vdw_cutoff = vdw_handler.cutoff * unit.angstrom

    electrostatics_handler = openff_sys.handlers[
        "Electrostatics"]  # Split this out
    if electrostatics_handler.method.lower() not in [
            v[1] for v in supported_cutoff_methods
    ]:
        raise UnsupportedCutoffMethodError()

    non_bonded_force = openmm.NonbondedForce()
    openmm_sys.addForce(non_bonded_force)

    for _ in openff_sys.topology.topology_particles:
        non_bonded_force.addParticle(0.0, 1.0, 0.0)

    if openff_sys.box is None:
        non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff)
    else:
        non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.PME)
        non_bonded_force.setUseDispersionCorrection(True)
        non_bonded_force.setCutoffDistance(vdw_cutoff)

    for vdw_atom, vdw_smirks in vdw_handler.slot_map.items():
        atom_idx = eval(vdw_atom)[0]

        partial_charge = electrostatics_handler.charge_map[vdw_atom]
        partial_charge = (partial_charge /
                          off_unit.elementary_charge).magnitude
        vdw_potential = vdw_handler.potentials[vdw_smirks]
        # these are floats, implicitly angstrom and kcal/mol
        sigma, epsilon = _lj_params_from_potential(vdw_potential)
        sigma = sigma * unit.angstrom / unit.nanometer
        epsilon = epsilon * unit.kilocalorie_per_mole / unit.kilojoule_per_mole

        non_bonded_force.setParticleParameters(atom_idx, partial_charge, sigma,
                                               epsilon)

    # from vdWHandler.postprocess_system
    bond_particle_indices = []

    for topology_molecule in openff_sys.topology.topology_molecules:

        top_mol_particle_start_index = topology_molecule.atom_start_topology_index

        for topology_bond in topology_molecule.bonds:

            top_index_1 = topology_molecule._ref_to_top_index[
                topology_bond.bond.atom1_index]
            top_index_2 = topology_molecule._ref_to_top_index[
                topology_bond.bond.atom2_index]

            top_index_1 += top_mol_particle_start_index
            top_index_2 += top_mol_particle_start_index

            bond_particle_indices.append((top_index_1, top_index_2))

    non_bonded_force.createExceptionsFromBonds(
        bond_particle_indices,
        electrostatics_handler.scale_14,
        vdw_handler.scale_14,
    )

    non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.PME)
    non_bonded_force.setCutoffDistance(9.0 * unit.angstrom)
    non_bonded_force.setEwaldErrorTolerance(1.0e-4)

    # It's not clear why this needs to happen here, but it cannot be set above
    # and satisfy vdW/Electrostatics methods Cutoff and PME; see create_force
    # and postprocess_system methods in toolkit
    if openff_sys.box is None:
        non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff)
Ejemplo n.º 14
0
def _process_nonbonded_forces(openff_sys, openmm_sys):
    """Process the vdW and Electrostatics sections of an OpenFF System into a corresponding openmm.NonbondedForce"""
    # Store the pairings, not just the supported methods for each
    supported_cutoff_methods = [["cutoff", "pme"]]

    if "vdW" in openff_sys.handlers:
        vdw_handler = openff_sys.handlers["vdW"]
        if vdw_handler.method not in [
                val[0] for val in supported_cutoff_methods
        ]:
            raise UnsupportedCutoffMethodError()

        vdw_cutoff = vdw_handler.cutoff * unit.angstrom

        electrostatics_handler = openff_sys.handlers[
            "Electrostatics"]  # Split this out
        if electrostatics_handler.method.lower() not in [
                v[1] for v in supported_cutoff_methods
        ]:
            raise UnsupportedCutoffMethodError()

        non_bonded_force = openmm.NonbondedForce()
        openmm_sys.addForce(non_bonded_force)

        for _ in openff_sys.topology.topology_particles:
            non_bonded_force.addParticle(0.0, 1.0, 0.0)

        if openff_sys.box is None:
            non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff)
        else:
            non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.PME)
            non_bonded_force.setUseDispersionCorrection(True)
            non_bonded_force.setCutoffDistance(vdw_cutoff)

        for top_key, pot_key in vdw_handler.slot_map.items():
            atom_idx = top_key.atom_indices[0]

            partial_charge = electrostatics_handler.charges[top_key]
            partial_charge = (partial_charge /
                              off_unit.elementary_charge).magnitude
            vdw_potential = vdw_handler.potentials[pot_key]
            # these are floats, implicitly angstrom and kcal/mol
            sigma, epsilon = _lj_params_from_potential(vdw_potential)
            sigma = sigma * unit.angstrom / unit.nanometer
            epsilon = epsilon * unit.kilocalorie_per_mole / unit.kilojoule_per_mole

            non_bonded_force.setParticleParameters(atom_idx, partial_charge,
                                                   sigma, epsilon)

    elif "Buckingham-6" in openff_sys.handlers:
        buck_handler = openff_sys.handlers["Buckingham-6"]

        non_bonded_force = openmm.CustomNonbondedForce(
            "A * exp(-B * r) - C * r ^ -6; A = sqrt(A1 * A2); B = 2 / (1 / B1 + 1 / B2); C = sqrt(C1 * C2)"
        )
        non_bonded_force.addPerParticleParameter("A")
        non_bonded_force.addPerParticleParameter("B")
        non_bonded_force.addPerParticleParameter("C")
        openmm_sys.addForce(non_bonded_force)

        for _ in openff_sys.topology.topology_particles:
            non_bonded_force.addParticle([0.0, 0.0, 0.0])

        if openff_sys.box is None:
            non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff)
        else:
            non_bonded_force.setNonbondedMethod(
                openmm.NonbondedForce.CutoffPeriodic)
            non_bonded_force.setCutoffDistance(buck_handler.cutoff *
                                               unit.angstrom)

        for top_key, pot_key in buck_handler.slot_map.items():
            atom_idx = top_key.atom_indices[0]

            # TODO: Add electrostatics
            params = buck_handler.potentials[pot_key].parameters
            a = pint_to_simtk(params["A"])
            b = pint_to_simtk(params["B"])
            c = pint_to_simtk(params["C"])
            non_bonded_force.setParticleParameters(atom_idx, [a, b, c])

        return

    # TODO: Figure out all of this post-processing with CustomNonbondedForce

    # from vdWHandler.postprocess_system
    bond_particle_indices = []

    for topology_molecule in openff_sys.topology.topology_molecules:

        top_mol_particle_start_index = topology_molecule.atom_start_topology_index

        for topology_bond in topology_molecule.bonds:

            top_index_1 = topology_molecule._ref_to_top_index[
                topology_bond.bond.atom1_index]
            top_index_2 = topology_molecule._ref_to_top_index[
                topology_bond.bond.atom2_index]

            top_index_1 += top_mol_particle_start_index
            top_index_2 += top_mol_particle_start_index

            bond_particle_indices.append((top_index_1, top_index_2))

    non_bonded_force.createExceptionsFromBonds(
        bond_particle_indices,
        electrostatics_handler.scale_14,
        vdw_handler.scale_14,
    )

    non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.PME)
    non_bonded_force.setCutoffDistance(9.0 * unit.angstrom)
    non_bonded_force.setEwaldErrorTolerance(1.0e-4)

    # It's not clear why this needs to happen here, but it cannot be set above
    # and satisfy vdW/Electrostatics methods Cutoff and PME; see create_force
    # and postprocess_system methods in toolkit
    if openff_sys.box is None:
        non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff)
Ejemplo n.º 15
0
def build_custom_system(reference_system,
                        receptor_atoms,
                        ligand_atoms,
                        valence_lambda,
                        coulomb_lambda,
                        vdw_lambda,
                        annihilate=False):
    """
    Build alchemically-modified system where ligand is decoupled or annihilated using Custom*Force classes.
    
    """

    # Create new system.
    system = openmm.System()

    # Set periodic box vectors.
    [a, b, c] = reference_system.getDefaultPeriodicBoxVectors()
    system.setDefaultPeriodicBoxVectors(a, b, c)

    # Add atoms.
    for atom_index in range(reference_system.getNumParticles()):
        mass = reference_system.getParticleMass(atom_index)
        system.addParticle(mass)

    # Add constraints
    for constraint_index in range(reference_system.getNumConstraints()):
        [iatom, jatom,
         r0] = reference_system.getConstraintParameters(constraint_index)
        system.addConstraint(iatom, jatom, r0)

    # Perturb force terms.
    for force_index in range(reference_system.getNumForces()):
        reference_force = reference_system.getForce(force_index)
        # Dispatch forces
        if isinstance(reference_force, openmm.HarmonicBondForce):
            # HarmonicBondForce
            force = openmm.HarmonicBondForce()
            for bond_index in range(reference_force.getNumBonds()):
                # Retrieve parameters.
                [iatom, jatom, r0,
                 K] = reference_force.getBondParameters(bond_index)
                # Annihilate if directed.
                if annihilate and (iatom
                                   in ligand_atoms) and (jatom
                                                         in ligand_atoms):
                    K *= valence_lambda
                # Add bond parameters.
                force.addBond(iatom, jatom, r0, K)
            # Add force to new system.
            system.addForce(force)
        elif isinstance(reference_force, openmm.HarmonicAngleForce):
            # HarmonicAngleForce
            force = openmm.HarmonicAngleForce()
            for angle_index in range(reference_force.getNumAngles()):
                # Retrieve parameters.
                [iatom, jatom, katom, theta0,
                 Ktheta] = reference_force.getAngleParameters(angle_index)
                # Annihilate if directed:
                if annihilate and (iatom in ligand_atoms) and (
                        jatom in ligand_atoms) and (katom in ligand_atoms):
                    Ktheta *= valence_lambda
                # Add parameters.
                force.addAngle(iatom, jatom, katom, theta0, Ktheta)
            # Add force to system.
            system.addForce(force)
        elif isinstance(reference_force, openmm.PeriodicTorsionForce):
            # PeriodicTorsionForce
            force = openmm.PeriodicTorsionForce()
            for torsion_index in range(reference_force.getNumTorsions()):
                # Retrieve parmaeters.
                [
                    particle1, particle2, particle3, particle4, periodicity,
                    phase, k
                ] = reference_force.getTorsionParameters(torsion_index)
                # Annihilate if directed:
                if annihilate and (particle1 in ligand_atoms) and (
                        particle2 in ligand_atoms) and (
                            particle3 in ligand_atoms) and (particle4
                                                            in ligand_atoms):
                    k *= valence_lambda
                # Add parameters.
                force.addTorsion(particle1, particle2, particle3, particle4,
                                 periodicity, phase, k)
            # Add force to system.
            system.addForce(force)
        elif isinstance(reference_force, openmm.NonbondedForce):
            # NonbondedForce will handle charges and exception interactions.
            force = openmm.NonbondedForce()
            for particle_index in range(reference_force.getNumParticles()):
                # Retrieve parameters.
                [charge, sigma, epsilon
                 ] = reference_force.getParticleParameters(particle_index)
                # Remove Lennard-Jones interactions, which will be handled by CustomNonbondedForce.
                epsilon *= 0.0
                # Alchemically modify charges.
                if particle_index in ligand_atoms:
                    charge *= coulomb_lambda
                # Add modified particle parameters.
                force.addParticle(charge, sigma, epsilon)
            for exception_index in range(reference_force.getNumExceptions()):
                # Retrieve parameters.
                [iatom, jatom, chargeprod, sigma, epsilon
                 ] = reference_force.getExceptionParameters(exception_index)
                # Alchemically modify epsilon and chargeprod.
                # Note that exceptions are handled by NonbondedForce and not CustomNonbondedForce.
                if (iatom in ligand_atoms) and (jatom in ligand_atoms):
                    if annihilate:
                        epsilon *= vdw_lambda
                        chargeprod *= coulomb_lambda
                # Add modified exception parameters.
                force.addException(iatom, jatom, chargeprod, sigma, epsilon)
            # Set parameters.
            force.setNonbondedMethod(reference_force.getNonbondedMethod())
            force.setCutoffDistance(reference_force.getCutoffDistance())
            force.setReactionFieldDielectric(
                reference_force.getReactionFieldDielectric())
            force.setEwaldErrorTolerance(
                reference_force.getEwaldErrorTolerance())
            # Add force to new system.
            system.addForce(force)

            # CustomNonbondedForce
            # Softcore potential.
            energy_expression = "4*epsilon*lambda*x*(x-1.0);"
            energy_expression += "x = 1.0/(alpha*(1.0-lambda) + (r/sigma)^6);"
            energy_expression += "epsilon = sqrt(epsilon1*epsilon2);"
            energy_expression += "sigma = 0.5*(sigma1 + sigma2);"
            energy_expression += "lambda = lambda1*lambda2;"

            force = openmm.CustomNonbondedForce(energy_expression)
            alpha = 0.5  # softcore parameter
            force.addGlobalParameter("alpha", alpha)
            force.addPerParticleParameter("sigma")
            force.addPerParticleParameter("epsilon")
            force.addPerParticleParameter("lambda")
            for particle_index in range(reference_force.getNumParticles()):
                # Retrieve parameters.
                [charge, sigma, epsilon
                 ] = reference_force.getParticleParameters(particle_index)
                # Alchemically modify parameters.
                if particle_index in ligand_atoms:
                    force.addParticle([sigma, epsilon, vdw_lambda])
                else:
                    force.addParticle([sigma, epsilon, 1.0])
            for exception_index in range(reference_force.getNumExceptions()):
                # Retrieve parameters.
                [iatom, jatom, chargeprod, sigma, epsilon
                 ] = reference_force.getExceptionParameters(exception_index)
                # All exceptions are handled by NonbondedForce, so we exclude all these here.
                force.addExclusion(iatom, jatom)
            if reference_force.getNonbondedMethod() in [
                    openmm.NonbondedForce.Ewald, openmm.NonbondedForce.PME
            ]:
                force.setNonbondedMethod(
                    openmm.CustomNonbondedForce.CutoffPeriodic)
            else:
                force.setNonbondedMethod(reference_force.getNonbondedMethod())
            force.setCutoffDistance(reference_force.getCutoffDistance())
            system.addForce(force)

        elif isinstance(reference_force, openmm.GBSAOBCForce):
            # GBSAOBCForce
            solvent_dielectric = reference_force.getSolventDielectric()
            solute_dielectric = reference_force.getSoluteDielectric()
            force = createCustomSoftcoreGBOBC(solvent_dielectric,
                                              solute_dielectric,
                                              igb=5)
            for particle_index in range(reference_force.getNumParticles()):
                # Retrieve parameters.
                [charge, radius, scaling_factor
                 ] = reference_force.getParticleParameters(particle_index)
                # Alchemically modify parameters.
                if particle_index in ligand_atoms:
                    # Scale charge and contribution to GB integrals.
                    force.addParticle([
                        charge * coulomb_lambda, radius, scaling_factor,
                        coulomb_lambda
                    ])
                else:
                    # Don't modulate GB.
                    force.addParticle([charge, radius, scaling_factor, 1.0])

            # Add force to new system.
            system.addForce(force)
        else:
            # Don't add unrecognized forces.
            pass

    return system
Ejemplo n.º 16
0
    def createSystem(self,
                     nonbondedMethod=None,
                     nonbondedCutoff=None,
                     removeCMMotion=True,
                     constraints=None):
        """Construct an OpenMM System representing the topology described by
        this prmtop infile.

        Parameters:
         - nonbondedMethod (object=NoCutoff) The method to use for nonbonded
         interactions.  Allowed values are
           NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME.
         - nonbondedCutoff (distance=1*nanometer) The cutoff distance to use for
         nonbonded interactions
         - constraints (object=None) Specifies which bonds and angles should be
         implemented with constraints. Allowed values are None, HBonds, AllBonds
         or HAngles.
         - rigidWater (boolean=True) If true, water molecules will be fully
         rigid regardless of the value passed for the constraints argument
         - implicitSolvent (object=None) If not None, the implicit solvent model
         to use.  The only allowed value is OBC2.
         - soluteDielectric (float=1.0) The solute dielectric constant to use in
         the implicit solvent model.
         - solventDielectric (float=78.5) The solvent dielectric constant to use
         in the implicit solvent model.
         - ewaldErrorTolerance (float=0.0005) The error tolerance to use if
         nonbondedMethod is Ewald or PME.
         - removeCMMotion (boolean=True) If true, a CMMotionRemover will be
         added to the System
         - hydrogenMass (mass=None) The mass to use for hydrogen atoms bound to
         heavy atoms.  Any mass added to a hydrogen is
           subtracted from the heavy atom to keep their total mass the same.
        Returns: the newly created System
        """
        # Create the System.

        syst = mm.System()
        nb = mm.NonbondedForce()
        nb.setNonbondedMethod(nonbondedMethod)
        nb.setCutoffDistance(nonbondedCutoff)
        syst.addForce(nb)

        boxSize = self.topology.getUnitCellDimensions()
        if boxSize is not None:
            syst.setDefaultPeriodicBoxVectors(
                (boxSize[0], 0, 0), (0, boxSize[1], 0), (0, 0, boxSize[2]))

        # Build a lookup table to let us process dihedrals more quickly.
        dihedralTypeTable, wildcardDihedralTypes = self._buildDihLookupTable()

        # Loop over molecules and create the specified number of each type.
        allAtomTypes = []
        allcharges = []
        allExceptions = []

        for moleculeName, moleculeCount in self.molecules:
            moleculeType = self.moleculeTypes[moleculeName]
            for i in range(moleculeCount):
                # Record the types of all atoms.
                baseAtomIndex = syst.getNumParticles()
                atomTypes = [atom[1] for atom in moleculeType.atoms]
                charges = [atom[6] for atom in moleculeType.atoms]
                for charge in charges:
                    allcharges.append(charge)
                for atomType in atomTypes:
                    allAtomTypes.append(atomType)

                try:
                    bondedTypes = [self.atomTypes[t][1] for t in atomTypes]
                except KeyError as e:
                    raise ValueError('Unknown atom type: ' + e.message)
                bondedTypes = [
                    b if b is not None else a
                    for a, b in zip(atomTypes, bondedTypes)
                ]
                # Add atoms.
                self._addAtomsToSystem(syst, moleculeType)

                # Add bonds.
                atomBonds = self._addBondsToSystem(syst, moleculeType,
                                                   bondedTypes, constraints,
                                                   baseAtomIndex)
                # Add constraints
                self._addConstraintsToSystem(syst, moleculeType, bondedTypes,
                                             constraints, baseAtomIndex)

                # Add angles.
                self._addAngleToSystem(syst, moleculeType, bondedTypes,
                                       atomBonds, baseAtomIndex)

                # Add torsions.
                self._addTorsionToSystem(syst, moleculeType, bondedTypes,
                                         dihedralTypeTable,
                                         wildcardDihedralTypes, baseAtomIndex)

                # Set nonbonded parameters for particles.
                exceptions = self._setnonbondedParams(nb, moleculeType,
                                                      baseAtomIndex, atomTypes)
                for exception in exceptions:
                    allExceptions.append(exception)

        # Add pairInteractions first as exceptions, followed by the rest
        # This way other exceptions can override pairInteractions
        for i in range(syst.getNumParticles() - 1):
            atomType1 = allAtomTypes[i]
            for j in range(i + 1, syst.getNumParticles()):
                atomType2 = allAtomTypes[j]

                try:
                    sig, eps = self.nonbondParams[(atomType1, atomType2)]
                except KeyError:
                    try:
                        sig, eps = self.nonbondParams[(atomType2, atomType1)]
                    except KeyError():
                        msg = "%s,%s pair interactions not found" % (atomType2,
                                                                     atomType1)
                        raise KeyError(msg)

                chargeProd = float(allcharges[i]) * float(allcharges[j])
                nb.addException(i, j, chargeProd, sig, eps, True)

        for exception in allExceptions:
            nb.addException(exception[0], exception[1], exception[2],
                            float(exception[3]), float(exception[4]), True)

        # Add a CMMotionRemover.
        if removeCMMotion:
            syst.addForce(mm.CMMotionRemover())

        return syst
Ejemplo n.º 17
0
    def export(system):
        '''
        Generate OpenMM system from a system

        Parameters
        ----------
        system : System

        Returns
        -------
        omm_system : simtk.openmm.System
        '''
        try:
            import simtk.openmm as mm
        except ImportError:
            raise ImportError('Can not import OpenMM')

        supported_terms = {
            LJ126Term, MieTerm, HarmonicBondTerm, HarmonicAngleTerm,
            SDKAngleTerm, PeriodicDihedralTerm, OplsImproperTerm,
            HarmonicImproperTerm, DrudeTerm
        }
        unsupported = system.ff_classes - supported_terms
        if unsupported != set():
            raise Exception(
                'Unsupported FF terms: %s' %
                (', '.join(map(lambda x: x.__name__, unsupported))))

        if system.vsite_types - {TIP4PSite} != set():
            raise Exception(
                'Virtual sites other than TIP4PSite haven\'t been implemented')

        top = system.topology
        ff = system.ff

        omm_system = mm.System()
        if system.use_pbc:
            omm_system.setDefaultPeriodicBoxVectors(*top.cell.vectors)
        for atom in top.atoms:
            omm_system.addParticle(atom.mass)

        ### Set up bonds #######################################################################
        for bond_class in system.bond_classes:
            if bond_class == HarmonicBondTerm:
                logger.info('Setting up harmonic bonds...')
                bforce = mm.HarmonicBondForce()
                for bond in top.bonds:
                    if bond.is_drude:
                        # DrudeForce will handle the bond between Drude pair
                        continue
                    bterm = system.bond_terms[id(bond)]
                    if type(bterm) != HarmonicBondTerm:
                        continue
                    bforce.addBond(bond.atom1.id, bond.atom2.id, bterm.length,
                                   bterm.k * 2)
            else:
                raise Exception('Bond terms other that HarmonicBondTerm '
                                'haven\'t been implemented')
            bforce.setUsesPeriodicBoundaryConditions(system.use_pbc)
            bforce.setForceGroup(ForceGroup.BOND)
            omm_system.addForce(bforce)

        ### Set up angles #######################################################################
        for angle_class in system.angle_classes:
            if angle_class == HarmonicAngleTerm:
                logger.info('Setting up harmonic angles...')
                aforce = mm.HarmonicAngleForce()
                for angle in top.angles:
                    aterm = system.angle_terms[id(angle)]
                    if type(aterm) == HarmonicAngleTerm:
                        aforce.addAngle(angle.atom1.id, angle.atom2.id,
                                        angle.atom3.id, aterm.theta * PI / 180,
                                        aterm.k * 2)
            elif angle_class == SDKAngleTerm:
                logger.info('Setting up SDK angles...')
                aforce = mm.CustomCompoundBondForce(
                    3, 'k*(theta-theta0)^2+step(rmin-r)*LJ96;'
                    'LJ96=6.75*epsilon*((sigma/r)^9-(sigma/r)^6)+epsilon;'
                    'theta=angle(p1,p2,p3);'
                    'r=distance(p1,p3);'
                    'rmin=1.144714*sigma')
                aforce.addPerBondParameter('theta0')
                aforce.addPerBondParameter('k')
                aforce.addPerBondParameter('epsilon')
                aforce.addPerBondParameter('sigma')
                for angle in top.angles:
                    aterm = system.angle_terms[id(angle)]
                    if type(aterm) != SDKAngleTerm:
                        continue
                    vdw = ff.get_vdw_term(ff.atom_types[angle.atom1.type],
                                          ff.atom_types[angle.atom2.type])
                    if type(
                            vdw
                    ) != MieTerm or vdw.repulsion != 9 or vdw.attraction != 6:
                        raise Exception(
                            f'Corresponding 9-6 MieTerm for {aterm} not found in FF'
                        )
                    aforce.addBond(
                        [angle.atom1.id, angle.atom2.id, angle.atom3.id], [
                            aterm.theta * PI / 180, aterm.k, vdw.epsilon,
                            vdw.sigma
                        ])
            else:
                raise Exception(
                    'Angle terms other that HarmonicAngleTerm and SDKAngleTerm '
                    'haven\'t been implemented')
            aforce.setUsesPeriodicBoundaryConditions(system.use_pbc)
            aforce.setForceGroup(ForceGroup.ANGLE)
            omm_system.addForce(aforce)

        ### Set up constraints #################################################################
        logger.info(
            f'Setting up {len(system.constrain_bonds)} bond constraints...')
        for bond in top.bonds:
            if id(bond) in system.constrain_bonds:
                omm_system.addConstraint(bond.atom1.id, bond.atom2.id,
                                         system.constrain_bonds[id(bond)])
        logger.info(
            f'Setting up {len(system.constrain_angles)} angle constraints...')
        for angle in top.angles:
            if id(angle) in system.constrain_angles:
                omm_system.addConstraint(angle.atom1.id, angle.atom3.id,
                                         system.constrain_angles[id(angle)])

        ### Set up dihedrals ###################################################################
        for dihedral_class in system.dihedral_classes:
            if dihedral_class == PeriodicDihedralTerm:
                logger.info('Setting up periodic dihedrals...')
                dforce = mm.PeriodicTorsionForce()
                for dihedral in top.dihedrals:
                    dterm = system.dihedral_terms[id(dihedral)]
                    ia1, ia2, ia3, ia4 = dihedral.atom1.id, dihedral.atom2.id, dihedral.atom3.id, dihedral.atom4.id
                    if type(dterm) == PeriodicDihedralTerm:
                        for par in dterm.parameters:
                            dforce.addTorsion(ia1, ia2, ia3, ia4, par.n,
                                              par.phi * PI / 180, par.k)
                    else:
                        continue
            else:
                raise Exception(
                    'Dihedral terms other that PeriodicDihedralTerm '
                    'haven\'t been implemented')
            dforce.setUsesPeriodicBoundaryConditions(system.use_pbc)
            dforce.setForceGroup(ForceGroup.DIHEDRAL)
            omm_system.addForce(dforce)

        ### Set up impropers ####################################################################
        for improper_class in system.improper_classes:
            if improper_class == OplsImproperTerm:
                logger.info('Setting up periodic impropers...')
                iforce = mm.CustomTorsionForce('k*(1-cos(2*theta))')
                iforce.addPerTorsionParameter('k')
                for improper in top.impropers:
                    iterm = system.improper_terms[id(improper)]
                    if type(iterm) == OplsImproperTerm:
                        # in OPLS convention, the third atom is the central atom
                        iforce.addTorsion(improper.atom2.id, improper.atom3.id,
                                          improper.atom1.id, improper.atom4.id,
                                          [iterm.k])
            elif improper_class == HarmonicImproperTerm:
                logger.info('Setting up harmonic impropers...')
                iforce = mm.CustomTorsionForce(f'k*min(dtheta,2*pi-dtheta)^2;'
                                               f'dtheta=abs(theta-phi0);'
                                               f'pi={PI}')
                iforce.addPerTorsionParameter('phi0')
                iforce.addPerTorsionParameter('k')
                for improper in top.impropers:
                    iterm = system.improper_terms[id(improper)]
                    if type(iterm) == HarmonicImproperTerm:
                        iforce.addTorsion(improper.atom1.id, improper.atom2.id,
                                          improper.atom3.id, improper.atom4.id,
                                          [iterm.phi * PI / 180, iterm.k])
            else:
                raise Exception(
                    'Improper terms other that PeriodicImproperTerm and '
                    'HarmonicImproperTerm haven\'t been implemented')
            iforce.setUsesPeriodicBoundaryConditions(system.use_pbc)
            iforce.setForceGroup(ForceGroup.IMPROPER)
            omm_system.addForce(iforce)

        ### Set up non-bonded interactions #########################################################
        # NonbonedForce is not flexible enough. Use it only for Coulomb interactions (including 1-4 Coulomb exceptions)
        # CustomNonbondedForce handles vdW interactions (including 1-4 LJ exceptions)
        cutoff = ff.vdw_cutoff
        logger.info('Setting up Coulomb interactions...')
        nbforce = mm.NonbondedForce()
        if system.use_pbc:
            nbforce.setNonbondedMethod(mm.NonbondedForce.PME)
            nbforce.setEwaldErrorTolerance(5E-4)
            nbforce.setCutoffDistance(cutoff)
            # dispersion will be handled by CustomNonbondedForce
            nbforce.setUseDispersionCorrection(False)
            try:
                nbforce.setExceptionsUsePeriodicBoundaryConditions(True)
            except:
                logger.warning('Cannot apply PBC for Coulomb 1-4 exceptions')
        else:
            nbforce.setNonbondedMethod(mm.NonbondedForce.NoCutoff)
        nbforce.setForceGroup(ForceGroup.COULOMB)
        omm_system.addForce(nbforce)
        for atom in top.atoms:
            nbforce.addParticle(atom.charge, 1.0, 0.0)

        ### Set up vdW interactions #########################################################
        atom_types = list(ff.atom_types.values())
        type_names = list(ff.atom_types.keys())
        n_type = len(atom_types)
        for vdw_class in system.vdw_classes:
            if vdw_class == LJ126Term:
                logger.info('Setting up LJ-12-6 vdW interactions...')
                if system.use_pbc and ff.vdw_long_range == ForceField.VDW_LONGRANGE_SHIFT:
                    invRc6 = 1 / cutoff**6
                    cforce = mm.CustomNonbondedForce(
                        f'A(type1,type2)*(invR6*invR6-{invRc6 * invRc6})-'
                        f'B(type1,type2)*(invR6-{invRc6});'
                        f'invR6=1/r^6')
                else:
                    cforce = mm.CustomNonbondedForce(
                        'A(type1,type2)*invR6*invR6-B(type1,type2)*invR6;'
                        'invR6=1/r^6')
                cforce.addPerParticleParameter('type')
                A_list = [0.0] * n_type * n_type
                B_list = [0.0] * n_type * n_type
                for i, atype1 in enumerate(atom_types):
                    for j, atype2 in enumerate(atom_types):
                        vdw = ff.get_vdw_term(atype1, atype2)
                        if type(vdw) == LJ126Term:
                            A = 4 * vdw.epsilon * vdw.sigma**12
                            B = 4 * vdw.epsilon * vdw.sigma**6
                        else:
                            A = B = 0
                        A_list[i + n_type * j] = A
                        B_list[i + n_type * j] = B
                cforce.addTabulatedFunction(
                    'A', mm.Discrete2DFunction(n_type, n_type, A_list))
                cforce.addTabulatedFunction(
                    'B', mm.Discrete2DFunction(n_type, n_type, B_list))

                for atom in top.atoms:
                    id_type = type_names.index(atom.type)
                    cforce.addParticle([id_type])

            elif vdw_class == MieTerm:
                logger.info('Setting up Mie vdW interactions...')
                if system.use_pbc and ff.vdw_long_range == ForceField.VDW_LONGRANGE_SHIFT:
                    cforce = mm.CustomNonbondedForce(
                        'A(type1,type2)/r^REP(type1,type2)-'
                        'B(type1,type2)/r^ATT(type1,type2)-'
                        'SHIFT(type1,type2)')
                else:
                    cforce = mm.CustomNonbondedForce(
                        'A(type1,type2)/r^REP(type1,type2)-'
                        'B(type1,type2)/r^ATT(type1,type2)')
                cforce.addPerParticleParameter('type')
                A_list = [0.0] * n_type * n_type
                B_list = [0.0] * n_type * n_type
                REP_list = [0.0] * n_type * n_type
                ATT_list = [0.0] * n_type * n_type
                SHIFT_list = [0.0] * n_type * n_type
                for i, atype1 in enumerate(atom_types):
                    for j, atype2 in enumerate(atom_types):
                        vdw = ff.get_vdw_term(atype1, atype2)
                        if type(vdw) == MieTerm:
                            A = vdw.factor_energy(
                            ) * vdw.epsilon * vdw.sigma**vdw.repulsion
                            B = vdw.factor_energy(
                            ) * vdw.epsilon * vdw.sigma**vdw.attraction
                            REP = vdw.repulsion
                            ATT = vdw.attraction
                            SHIFT = A / cutoff**REP - B / cutoff**ATT
                        else:
                            A = B = REP = ATT = SHIFT = 0
                        A_list[i + n_type * j] = A
                        B_list[i + n_type * j] = B
                        REP_list[i + n_type * j] = REP
                        ATT_list[i + n_type * j] = ATT
                        SHIFT_list[i + n_type * j] = SHIFT
                cforce.addTabulatedFunction(
                    'A', mm.Discrete2DFunction(n_type, n_type, A_list))
                cforce.addTabulatedFunction(
                    'B', mm.Discrete2DFunction(n_type, n_type, B_list))
                cforce.addTabulatedFunction(
                    'REP', mm.Discrete2DFunction(n_type, n_type, REP_list))
                cforce.addTabulatedFunction(
                    'ATT', mm.Discrete2DFunction(n_type, n_type, ATT_list))
                if system.use_pbc and ff.vdw_long_range == ForceField.VDW_LONGRANGE_SHIFT:
                    cforce.addTabulatedFunction(
                        'SHIFT',
                        mm.Discrete2DFunction(n_type, n_type, SHIFT_list))

                for atom in top.atoms:
                    id_type = type_names.index(atom.type)
                    cforce.addParticle([id_type])

            else:
                raise Exception('vdW terms other than LJ126Term and MieTerm '
                                'haven\'t been implemented')
            if system.use_pbc:
                cforce.setNonbondedMethod(
                    mm.CustomNonbondedForce.CutoffPeriodic)
                cforce.setCutoffDistance(cutoff)
                if ff.vdw_long_range == ForceField.VDW_LONGRANGE_CORRECT:
                    cforce.setUseLongRangeCorrection(True)
            else:
                cforce.setNonbondedMethod(mm.CustomNonbondedForce.NoCutoff)
            cforce.setForceGroup(ForceGroup.VDW)
            omm_system.addForce(cforce)

        ### Set up 1-2, 1-3 and 1-4 exceptions ##################################################
        logger.info('Setting up 1-2, 1-3 and 1-4 exceptions...')
        custom_nb_forces = [
            f for f in omm_system.getForces()
            if type(f) == mm.CustomNonbondedForce
        ]
        pair12, pair13, pair14 = top.get_12_13_14_pairs()
        for atom1, atom2 in pair12 + pair13:
            nbforce.addException(atom1.id, atom2.id, 0.0, 1.0, 0.0)
            for f in custom_nb_forces:
                f.addExclusion(atom1.id, atom2.id)
        # As long as 1-4 LJ OR Coulomb need to be scaled, then this pair should be excluded from ALL non-bonded forces.
        # This is required by OpenMM's internal implementation.
        # Even though NonbondedForce can handle 1-4 vdW, we use it only for 1-4 Coulomb.
        # And use CustomBondForce to handle 1-4 vdW, which makes it more clear for energy decomposition.
        if ff.scale_14_vdw != 1 or ff.scale_14_coulomb != 1:
            pair14_forces = {}  # {VdwTerm: mm.NbForce}
            for atom1, atom2 in pair14:
                charge_prod = atom1.charge * atom2.charge * ff.scale_14_coulomb
                nbforce.addException(atom1.id, atom2.id, charge_prod, 1.0, 0.0)
                for f in custom_nb_forces:
                    f.addExclusion(atom1.id, atom2.id)
                if ff.scale_14_vdw == 0:
                    continue
                vdw = ff.get_vdw_term(ff.atom_types[atom1.type],
                                      ff.atom_types[atom2.type])
                # We generalize LJ126Term and MieTerm because of minimal computational cost for 1-4 vdW
                if type(vdw) in (LJ126Term, MieTerm):
                    cbforce = pair14_forces.get(MieTerm)
                    if cbforce is None:
                        cbforce = mm.CustomBondForce(
                            'C*epsilon*((sigma/r)^n-(sigma/r)^m);'
                            'C=n/(n-m)*(n/m)^(m/(n-m))')
                        cbforce.addPerBondParameter('epsilon')
                        cbforce.addPerBondParameter('sigma')
                        cbforce.addPerBondParameter('n')
                        cbforce.addPerBondParameter('m')
                        cbforce.setUsesPeriodicBoundaryConditions(
                            system.use_pbc)
                        cbforce.setForceGroup(ForceGroup.VDW)
                        omm_system.addForce(cbforce)
                        pair14_forces[MieTerm] = cbforce
                    epsilon = vdw.epsilon * ff.scale_14_vdw
                    if type(vdw) == LJ126Term:
                        cbforce.addBond(atom1.id, atom2.id,
                                        [epsilon, vdw.sigma, 12, 6])
                    elif type(vdw) == MieTerm:
                        cbforce.addBond(atom1.id, atom2.id, [
                            epsilon, vdw.sigma, vdw.repulsion, vdw.attraction
                        ])
                else:
                    raise Exception(
                        '1-4 scaling for vdW terms other than LJ126Term and MieTerm '
                        'haven\'t been implemented')

        ### Set up Drude particles ##############################################################
        for polar_class in system.polarizable_classes:
            if polar_class == DrudeTerm:
                logger.info('Setting up Drude polarizations...')
                pforce = mm.DrudeForce()
                pforce.setForceGroup(ForceGroup.DRUDE)
                omm_system.addForce(pforce)
                parent_idx_thole = {
                }  # {parent: (index in DrudeForce, thole)} for addScreenPair
                for parent, drude in system.drude_pairs.items():
                    pterm = system.polarizable_terms[parent]
                    n_H = len([
                        atom for atom in parent.bond_partners
                        if atom.symbol == 'H'
                    ])
                    alpha = pterm.alpha + n_H * pterm.merge_alpha_H
                    idx = pforce.addParticle(drude.id, parent.id, -1, -1, -1,
                                             drude.charge, alpha, 0, 0)
                    parent_idx_thole[parent] = (idx, pterm.thole)

                # exclude the non-boned interactions between Drude and parent
                # and those concerning Drude particles in 1-2 and 1-3 pairs
                # pairs formed by real atoms have already been handled above
                # also apply thole screening between 1-2 and 1-3 Drude dipole pairs
                drude_exclusions = list(system.drude_pairs.items())
                for atom1, atom2 in pair12 + pair13:
                    drude1 = system.drude_pairs.get(atom1)
                    drude2 = system.drude_pairs.get(atom2)
                    if drude1 is not None:
                        drude_exclusions.append((drude1, atom2))
                    if drude2 is not None:
                        drude_exclusions.append((atom1, drude2))
                    if drude1 is not None and drude2 is not None:
                        drude_exclusions.append((drude1, drude2))
                        idx1, thole1 = parent_idx_thole[atom1]
                        idx2, thole2 = parent_idx_thole[atom2]
                        pforce.addScreenedPair(idx1, idx2,
                                               (thole1 + thole2) / 2)
                for a1, a2 in drude_exclusions:
                    nbforce.addException(a1.id, a2.id, 0, 1.0, 0)
                    for f in custom_nb_forces:
                        f.addExclusion(a1.id, a2.id)

                # scale the non-boned interactions concerning Drude particles in 1-4 pairs
                # pairs formed by real atoms have already been handled above
                drude_exceptions14 = []
                for atom1, atom2 in pair14:
                    drude1 = system.drude_pairs.get(atom1)
                    drude2 = system.drude_pairs.get(atom2)
                    if drude1 is not None:
                        drude_exceptions14.append((drude1, atom2))
                    if drude2 is not None:
                        drude_exceptions14.append((atom1, drude2))
                    if drude1 is not None and drude2 is not None:
                        drude_exceptions14.append((drude1, drude2))
                for a1, a2 in drude_exceptions14:
                    charge_prod = a1.charge * a2.charge * ff.scale_14_coulomb
                    nbforce.addException(a1.id, a2.id, charge_prod, 1.0, 0.0)
                    for f in custom_nb_forces:
                        f.addExclusion(a1.id, a2.id)
            else:
                raise Exception(
                    'Polarizable terms other that DrudeTerm haven\'t been implemented'
                )

        ### Set up virtual sites ################################################################
        if top.has_virtual_site:
            logger.info('Setting up virtual sites...')
            for atom in top.atoms:
                vsite = atom.virtual_site
                if type(vsite) == TIP4PSite:
                    O, H1, H2 = vsite.parents
                    coeffs = system.get_TIP4P_linear_coeffs(atom)
                    omm_vsite = mm.ThreeParticleAverageSite(
                        O.id, H1.id, H2.id, *coeffs)
                    omm_system.setVirtualSite(atom.id, omm_vsite)
                elif vsite is not None:
                    raise Exception(
                        'Virtual sites other than TIP4PSite haven\'t been implemented'
                    )

            # exclude the non-boned interactions between virtual sites and parents
            # and particles (atoms, drude particles, virtual sites) in 1-2 and 1-3 pairs
            # TODO Assume no more than one virtual site is attached to each atom
            vsite_exclusions = list(system.vsite_pairs.items())
            for atom, vsite in system.vsite_pairs.items():
                drude = system.drude_pairs.get(atom)
                if drude is not None:
                    vsite_exclusions.append((vsite, drude))
            for atom1, atom2 in pair12 + pair13:
                vsite1 = system.vsite_pairs.get(atom1)
                vsite2 = system.vsite_pairs.get(atom2)
                drude1 = system.drude_pairs.get(atom1)
                drude2 = system.drude_pairs.get(atom2)
                if vsite1 is not None:
                    vsite_exclusions.append((vsite1, atom2))
                    if drude2 is not None:
                        vsite_exclusions.append((vsite1, drude2))
                if vsite2 is not None:
                    vsite_exclusions.append((vsite2, atom1))
                    if drude1 is not None:
                        vsite_exclusions.append((vsite2, drude1))
                if None not in [vsite1, vsite2]:
                    vsite_exclusions.append((vsite1, vsite2))
            for a1, a2 in vsite_exclusions:
                nbforce.addException(a1.id, a2.id, 0, 1.0, 0)
                for f in custom_nb_forces:
                    f.addExclusion(a1.id, a2.id)

            # scale the non-boned interactions between virtual sites and particles in 1-4 pairs
            # TODO Assume no 1-4 LJ interactions on virtual sites
            vsite_exceptions14 = []
            for atom1, atom2 in pair14:
                vsite1 = system.vsite_pairs.get(atom1)
                vsite2 = system.vsite_pairs.get(atom2)
                drude1 = system.drude_pairs.get(atom1)
                drude2 = system.drude_pairs.get(atom2)
                if vsite1 is not None:
                    vsite_exceptions14.append((vsite1, atom2))
                    if drude2 is not None:
                        vsite_exceptions14.append((vsite1, drude2))
                if vsite2 is not None:
                    vsite_exceptions14.append((vsite2, atom1))
                    if drude1 is not None:
                        vsite_exceptions14.append((vsite2, drude1))
                if None not in [vsite1, vsite2]:
                    vsite_exceptions14.append((vsite1, vsite2))
            for a1, a2 in vsite_exceptions14:
                charge_prod = a1.charge * a2.charge * ff.scale_14_coulomb
                nbforce.addException(a1.id, a2.id, charge_prod, 1.0, 0.0)
                for f in custom_nb_forces:
                    f.addExclusion(a1.id, a2.id)

        ### Remove COM motion ###################################################################
        logger.info('Setting up COM motion remover...')
        omm_system.addForce(mm.CMMotionRemover(10))

        return omm_system
Ejemplo n.º 18
0
    def create_system(self, parameters=None, nmolecules=512, verbose=False):
        """
        Construct a flexible TIP3P system.
        
        RETURNS
        
        system (simtk.openmm.System) - TIP3P system with given parameters
        
        EXAMPLES

        Create with default parameters.
        
        >>> system = TIP3P.create_system()

        Create with specified parameters.
        
        >>> parameters = TIP3P.get_default_parameters()
        >>> system = TIP3P.create_system(parameters)

        """

        initial_time = time.time()

        # Fixed parameters.
        massO = 16.0 * units.amu  # oxygen mass
        massH = 1.0 * units.amu  # hydrogen mass
        cutoff = None  # override for nonbonded cutoff
        nonbonded_method = mm.NonbondedForce.PME  # nonbonded method
        unit_sigma = 1.0 * units.angstrom  # hydrogen sigma
        zero_epsilon = 0.0 * units.kilocalories_per_mole  # hydrogen epsilon

        # Set parameters if not provided.
        if parameters is None:
            parameters = TIP3P.get_default_parameters()

        # Create system.
        system = mm.System()

        # Masses.
        for molecule_index in range(nmolecules):
            system.addParticle(massO)
            system.addParticle(massH)
            system.addParticle(massH)

        # Nonbonded interactions.
        nb = mm.NonbondedForce()
        nb.setNonbondedMethod(nonbonded_method)
        if cutoff is not None: nb.setCutoffDistance(cutoff)
        for molecule_index in range(nmolecules):
            # Nonbonded parameters.
            nb.addParticle(parameters['qO'], parameters['sigma'],
                           parameters['epsilon'])
            nb.addParticle(parameters['qH'], unit_sigma, zero_epsilon)
            nb.addParticle(parameters['qH'], unit_sigma, zero_epsilon)

            # Nonbonded exceptions.
            nb.addException(molecule_index * 3, molecule_index * 3 + 1, 0.0,
                            unit_sigma, zero_epsilon)
            nb.addException(molecule_index * 3, molecule_index * 3 + 2, 0.0,
                            unit_sigma, zero_epsilon)
            nb.addException(molecule_index * 3 + 1, molecule_index * 3 + 2,
                            0.0, unit_sigma, zero_epsilon)
        system.addForce(nb)

        # Bonds.
        bonds = mm.HarmonicBondForce()
        for molecule_index in range(nmolecules):
            bonds.addBond(3 * molecule_index + 0, 3 * molecule_index + 1,
                          parameters['rOH'], parameters['kOH'])
            bonds.addBond(3 * molecule_index + 0, 3 * molecule_index + 2,
                          parameters['rOH'], parameters['kOH'])
        system.addForce(bonds)

        # Angles.
        angles = mm.HarmonicAngleForce()
        for molecule_index in range(nmolecules):
            angles.addAngle(3 * molecule_index + 1, 3 * molecule_index + 0,
                            3 * molecule_index + 2, parameters['aHOH'],
                            parameters['kHOH'])
        system.addForce(angles)

        final_time = time.time()
        elapsed_time = final_time - initial_time
        if verbose: print "%.3f s elapsed" % elapsed_time

        return system
Ejemplo n.º 19
0
def lennard_jones_force(
        sim_object,
        cutoff=2.5,
        domains=False,
        epsilonRep=0.24,
        epsilonAttr=0.27,
        blindFraction=(-1),
        sigmaRep=None,
        sigmaAttr=None,
):
    """
    Adds a lennard-jones force, that allows for mutual attraction.
    This is the slowest force out of all repulsive.

    .. note ::
        This is the only force that allows for so-called "exceptions'.
        Exceptions allow you to change parameters of the force
        for a specific pair of particles.
        This can be used to create short-range attraction between
        pairs of particles.
        See manual for Openmm.NonbondedForce.addException.

    Parameters
    ----------

    cutoff : float, optional
        Radius cutoff value. Default is good.
    domains : bool, optional
        Use domains, defined by
        :py:func:'setDomains <Simulation.setDomains>'
    epsilonRep : float, optional
        Epsilon (attraction strength) for LJ-force for all particles
        (except for domain) in kT
    epsilonAttr : float, optional
        Epsilon for attractive domain (if domains are used) in kT
    blindFraction : float, 0<x<1
        Fraction of particles that are "transparent" -
        used here instead of truncation
    sigmaRep, sigmaAttr: float, optional
        Radius of particles in the LJ force. For advanced fine-tuning.

     """
    sim_object.metadata["LennardJonesForce"] = repr({
        "cutoff":
        cutoff,
        "domains":
        domains,
        "epsilonRep":
        epsilonRep,
        "epsilonAttr":
        epsilonAttr,
        "blindFraction":
        blindFraction,
    })

    if blindFraction > 0.99:
        sim_object._exitProgram(
            "why do you need this force without particles???"
            " set blindFraction between 0 and 1")
    if (sigmaRep is None) and (sigmaAttr is None):
        sigmaAttr = sigmaRep = sim_object.conlen
    else:
        sigmaAttr = sigmaAttr * sim_object.conlen
        sigmaRep = sigmaRep * sim_object.conlen

    epsilonRep = epsilonRep * sim_object.kT
    epsilonAttr = epsilonAttr * sim_object.kT

    nbCutOffDist = sim_object.conlen * cutoff
    sim_object.epsilonRep = epsilonRep
    repulforce = openmm.NonbondedForce()

    sim_object.force_dict["Nonbonded"] = repulforce
    for i in range(sim_object.N):
        particleParameters = [0.0, 0.0, 0.0]

        if np.random.random() > blindFraction:
            particleParameters[1] = sigmaRep
            particleParameters[2] = epsilonRep

            if domains == True:
                if sim_object.domains[i] != 0:
                    particleParameters[1] = sigmaAttr
                    particleParameters[2] = epsilonAttr

        repulforce.addParticle(*particleParameters)

    repulforce.setCutoffDistance(nbCutOffDist)
Ejemplo n.º 20
0
def _process_nonbonded_forces(openff_sys,
                              openmm_sys,
                              combine_nonbonded_forces=False):
    """Process the vdW and Electrostatics sections of an OpenFF Interchange into a corresponding openmm.NonbondedForce
    or a collection of other forces (NonbondedForce, CustomNonbondedForce, CustomBondForce)"""
    if "vdW" in openff_sys.handlers:
        vdw_handler = openff_sys.handlers["vdW"]

        vdw_cutoff = vdw_handler.cutoff.m_as(off_unit.angstrom) * unit.angstrom
        vdw_method = vdw_handler.method.lower()

        electrostatics_handler = openff_sys.handlers["Electrostatics"]
        electrostatics_method = electrostatics_handler.method.lower()

        if vdw_handler.mixing_rule != "lorentz-berthelot":
            if combine_nonbonded_forces:
                raise UnsupportedExportError(
                    "OpenMM's default NonbondedForce only supports Lorentz-Berthelot mixing rules."
                    "Try setting `combine_nonbonded_forces=False`.")
            else:
                raise NotImplementedError(
                    f"Mixing rule `{vdw_handler.mixing_rule}` not compatible with current OpenMM export."
                    "The only supported values is `lorentez-berthelot`.")

        if vdw_handler.mixing_rule == "lorentz-berthelot":
            if not combine_nonbonded_forces:
                mixing_rule_expression = (
                    "sigma=(sigma1+sigma2)/2; epsilon=sqrt(epsilon1*epsilon2); "
                )

        if combine_nonbonded_forces:
            non_bonded_force = openmm.NonbondedForce()
            openmm_sys.addForce(non_bonded_force)

            for _ in openff_sys.topology.mdtop.atoms:
                non_bonded_force.addParticle(0.0, 1.0, 0.0)

            if vdw_method == "cutoff" and electrostatics_method == "pme":
                if openff_sys.box is not None:
                    non_bonded_force.setNonbondedMethod(
                        openmm.NonbondedForce.PME)
                    non_bonded_force.setUseDispersionCorrection(True)
                    non_bonded_force.setCutoffDistance(vdw_cutoff)
                    non_bonded_force.setEwaldErrorTolerance(1.0e-4)
                else:
                    raise UnsupportedCutoffMethodError
            elif vdw_method == "pme" and electrostatics_method == "pme":
                if openff_sys.box is not None:
                    non_bonded_force.setNonbondedMethod(
                        openmm.NonbondedForce.LJPME)
                    non_bonded_force.setEwaldErrorTolerance(1.0e-4)
                else:
                    raise UnsupportedCutoffMethodError
            else:
                raise UnimplementedCutoffMethodError(
                    f"Combination of non-bonded cutoff methods {vdw_cutoff} (vdW) and "
                    f"{electrostatics_method} (Electrostatics) not currently supported with "
                    f"`combine_nonbonded_forces={combine_nonbonded_forces}")

        else:
            vdw_expression = vdw_handler.expression
            vdw_expression = vdw_expression.replace("**", "^")

            vdw_force = openmm.CustomNonbondedForce(vdw_expression + "; " +
                                                    mixing_rule_expression)
            openmm_sys.addForce(vdw_force)
            vdw_force.addPerParticleParameter("sigma")
            vdw_force.addPerParticleParameter("epsilon")

            # TODO: Add virtual particles
            for _ in openff_sys.topology.mdtop.atoms:
                vdw_force.addParticle([1.0, 0.0])

            if vdw_method == "cutoff":
                if openff_sys.box is None:
                    vdw_force.setNonbondedMethod(
                        openmm.NonbondedForce.CutoffNonPeriodic)
                else:
                    vdw_force.setNonbondedMethod(
                        openmm.NonbondedForce.CutoffPeriodic)
                vdw_force.setUseLongRangeCorrection(True)
                vdw_force.setCutoffDistance(vdw_cutoff)
                if getattr(vdw_handler, "switch_width", None) is not None:
                    if vdw_handler.switch_width == 0.0:
                        vdw_force.setUseSwitchingFunction(False)
                    else:
                        switching_distance = (vdw_handler.cutoff -
                                              vdw_handler.switch_width)
                        if switching_distance.m < 0:
                            raise UnsupportedCutoffMethodError(
                                "Found a 'switch_width' greater than the cutoff distance. It's not clear "
                                "what this means and it's probably invalid. Found "
                                f"switch_width{vdw_handler.switch_width} and cutoff {vdw_handler.cutoff}"
                            )

                        switching_distance = (
                            switching_distance.m_as(off_unit.angstrom) *
                            unit.angstrom)

                        vdw_force.setUseSwitchingFunction(True)
                        vdw_force.setSwitchingDistance(switching_distance)

            elif vdw_method == "pme":
                if openff_sys.box is None:
                    raise UnsupportedCutoffMethodError(
                        "vdW method pme/ljpme is not valid for non-periodic systems."
                    )
                else:
                    # TODO: Fully flesh out this implementation - cutoffs, other settings
                    vdw_force.setNonbondedMethod(openmm.NonbondedForce.PME)

            electrostatics_force = openmm.NonbondedForce()
            openmm_sys.addForce(electrostatics_force)

            for _ in openff_sys.topology.mdtop.atoms:
                electrostatics_force.addParticle(0.0, 1.0, 0.0)

            if electrostatics_method == "reaction-field":
                if openff_sys.box is None:
                    # TODO: Should this state be prevented from happening?
                    raise UnsupportedCutoffMethodError(
                        f"Electrostatics method {electrostatics_method} is not valid for a non-periodic interchange."
                    )
                else:
                    raise UnimplementedCutoffMethodError(
                        f"Electrostatics method {electrostatics_method} is not yet implemented."
                    )
            elif electrostatics_method == "pme":
                electrostatics_force.setNonbondedMethod(
                    openmm.NonbondedForce.PME)
                electrostatics_force.setEwaldErrorTolerance(1.0e-4)
                electrostatics_force.setUseDispersionCorrection(True)
            elif electrostatics_method == "cutoff":
                raise UnsupportedCutoffMethodError(
                    "OpenMM does not clearly support cut-off electrostatics with no reaction-field attenuation."
                )
            else:
                raise UnsupportedCutoffMethodError(
                    f"Electrostatics method {electrostatics_method} not supported"
                )

        partial_charges = electrostatics_handler.charges

        for top_key, pot_key in vdw_handler.slot_map.items():
            atom_idx = top_key.atom_indices[0]

            partial_charge = partial_charges[top_key]
            # partial_charge = partial_charge.m_as(off_unit.elementary_charge)
            vdw_potential = vdw_handler.potentials[pot_key]
            # these are floats, implicitly angstrom and kcal/mol
            sigma, epsilon = _lj_params_from_potential(vdw_potential)
            sigma = sigma.m_as(off_unit.nanometer)
            epsilon = epsilon.m_as(off_unit.kilojoule / off_unit.mol)

            if combine_nonbonded_forces:
                non_bonded_force.setParticleParameters(
                    atom_idx,
                    partial_charge.m_as(off_unit.e),
                    sigma,
                    epsilon,
                )
            else:
                vdw_force.setParticleParameters(atom_idx, [sigma, epsilon])
                electrostatics_force.setParticleParameters(
                    atom_idx, partial_charge.m_as(off_unit.e), 0.0, 0.0)

    elif "Buckingham-6" in openff_sys.handlers:
        buck_handler = openff_sys.handlers["Buckingham-6"]

        non_bonded_force = openmm.CustomNonbondedForce(
            "A * exp(-B * r) - C * r ^ -6; A = sqrt(A1 * A2); B = 2 / (1 / B1 + 1 / B2); C = sqrt(C1 * C2)"
        )
        non_bonded_force.addPerParticleParameter("A")
        non_bonded_force.addPerParticleParameter("B")
        non_bonded_force.addPerParticleParameter("C")
        openmm_sys.addForce(non_bonded_force)

        for _ in openff_sys.topology.mdtop.atoms:
            non_bonded_force.addParticle([0.0, 0.0, 0.0])

        if openff_sys.box is None:
            non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff)
        else:
            non_bonded_force.setNonbondedMethod(
                openmm.NonbondedForce.CutoffPeriodic)
            non_bonded_force.setCutoffDistance(buck_handler.cutoff *
                                               unit.angstrom)

        for top_key, pot_key in buck_handler.slot_map.items():
            atom_idx = top_key.atom_indices[0]

            # TODO: Add electrostatics
            params = buck_handler.potentials[pot_key].parameters
            a = pint_to_simtk(params["A"])
            b = pint_to_simtk(params["B"])
            c = pint_to_simtk(params["C"])
            non_bonded_force.setParticleParameters(atom_idx, [a, b, c])

        return

    if not combine_nonbonded_forces:
        # Attempting to match the value used internally by OpenMM; The source of this value is likely
        # https://github.com/openmm/openmm/issues/1149#issuecomment-250299854
        # 1 / * (4pi * eps0) * elementary_charge ** 2 / nanometer ** 2
        coul_const = 138.935456  # kJ/nm

        vdw_14_force = openmm.CustomBondForce(
            "4*epsilon*((sigma/r)^12-(sigma/r)^6)")
        vdw_14_force.addPerBondParameter("sigma")
        vdw_14_force.addPerBondParameter("epsilon")
        vdw_14_force.setUsesPeriodicBoundaryConditions(True)
        coul_14_force = openmm.CustomBondForce(f"{coul_const}*qq/r")
        coul_14_force.addPerBondParameter("qq")
        coul_14_force.setUsesPeriodicBoundaryConditions(True)

        openmm_sys.addForce(vdw_14_force)
        openmm_sys.addForce(coul_14_force)

    # Need to create 1-4 exceptions, just to have a baseline for splitting out/modifying
    # It might be simpler to iterate over 1-4 pairs directly
    bonds = [(b.atom1.index, b.atom2.index)
             for b in openff_sys.topology.mdtop.bonds]

    if combine_nonbonded_forces:
        non_bonded_force.createExceptionsFromBonds(
            bonds=bonds,
            coulomb14Scale=electrostatics_handler.scale_14,
            lj14Scale=vdw_handler.scale_14,
        )
    else:
        electrostatics_force.createExceptionsFromBonds(
            bonds=bonds,
            coulomb14Scale=electrostatics_handler.scale_14,
            lj14Scale=vdw_handler.scale_14,
        )

        for i in range(electrostatics_force.getNumExceptions()):
            (p1, p2, q, sig,
             eps) = electrostatics_force.getExceptionParameters(i)

            # If the interactions are both zero, assume this is a 1-2 or 1-3 interaction
            if q._value == 0 and eps._value == 0:
                pass
            else:
                # Assume this is a 1-4 interaction
                # Look up the vdW parameters for each particle
                sig1, eps1 = vdw_force.getParticleParameters(p1)
                sig2, eps2 = vdw_force.getParticleParameters(p2)
                q1, _, _ = electrostatics_force.getParticleParameters(p1)
                q2, _, _ = electrostatics_force.getParticleParameters(p2)

                # manually compute and set the 1-4 interactions
                sig_14 = (sig1 + sig2) * 0.5
                eps_14 = (eps1 * eps2)**0.5 * vdw_handler.scale_14
                qq = q1 * q2 * electrostatics_handler.scale_14

                vdw_14_force.addBond(p1, p2, [sig_14, eps_14])
                coul_14_force.addBond(p1, p2, [qq])
            vdw_force.addExclusion(p1, p2)
            # electrostatics_force.addExclusion(p1, p2)
            electrostatics_force.setExceptionParameters(
                i, p1, p2, 0.0, 0.0, 0.0)
Ejemplo n.º 21
0
    def __init__(self,
                 atom_1='Ar',
                 atom_2='Xe',
                 mass_1=None,
                 sigma_1=None,
                 epsilon_1=None,
                 mass_2=None,
                 sigma_2=None,
                 epsilon_2=None,
                 cutoff_distance=None,
                 switching_distance=None,
                 box=None,
                 coordinates=None):

        super().__init__()

        # Parameters

        if (mass_1 is not None) or (sigma_1 is not None) or (epsilon_1
                                                             is not None):
            if mass_1 is None:
                raise ValueError(
                    'A value for the input argument "mass_1" is needed.')
            if sigma_1 is None:
                raise ValueError(
                    'A value for the input argument "sigma_1" is needed.')
            if epsilon_1 is None:
                raise ValueError(
                    'A value for the input argument "epsilon_1" is needed.')
        elif atom_1 is not None:
            mass_1 = atoms_LJ[atom_1]['mass']
            sigma_1 = atoms_LJ[atom_1]['sigma']
            epsilon_1 = atoms_LJ[atom_1]['epsilon']

        if (mass_2 is not None) or (sigma_2 is not None) or (epsilon_2
                                                             is not None):
            if mass_2 is None:
                raise ValueError(
                    'A value for the input argument "mass_2" is needed.')
            if sigma_2 is None:
                raise ValueError(
                    'A value for the input argument "sigma_2" is needed.')
            if epsilon_2 is None:
                raise ValueError(
                    'A value for the input argument "epsilon_2" is needed.')
        elif atom_2 is not None:
            mass_2 = atoms_LJ[atom_2]['mass']
            sigma_2 = atoms_LJ[atom_2]['sigma']
            epsilon_2 = atoms_LJ[atom_2]['epsilon']

        self.parameters['mass_1'] = mass_1
        self.parameters['sigma_1'] = sigma_1
        self.parameters['epsilon_1'] = epsilon_1

        self.parameters['mass_2'] = mass_2
        self.parameters['sigma_2'] = sigma_2
        self.parameters['epsilon_2'] = epsilon_2

        if box is not None:

            reduced_sigma = self.get_reduced_sigma()

            if cutoff_distance is None:
                cutoff_distance = 4.0 * reduced_sigma

            if switching_distance is None:
                switching_distance = 3.0 * reduced_sigma

        self.parameters['box'] = box
        self.parameters['cutoff_distance'] = cutoff_distance
        self.parameters['switching_distance'] = switching_distance

        # OpenMM topology

        self.topology = app.Topology()

        try:
            dummy_element = app.element.get_by_symbol('DUM')
        except:
            dummy_element = app.Element(0, 'DUM', 'DUM', 0.0 * unit.amu)

        chain = self.topology.addChain('A')
        residue = self.topology.addResidue('DUM_0', chain)
        atom = self.topology.addAtom(name='DUM_0',
                                     element=dummy_element,
                                     residue=residue)
        residue = self.topology.addResidue('DUM_1', chain)
        atom = self.topology.addAtom(name='DUM_1',
                                     element=dummy_element,
                                     residue=residue)

        # OpenMM system

        self.system = mm.System()

        non_bonded_force = mm.NonbondedForce()

        if box is not None:
            non_bonded_force.setNonbondedMethod(
                mm.NonbondedForce.CutoffPeriodic)
            non_bonded_force.setUseSwitchingFunction(True)
            non_bonded_force.setCutoffDistance(cutoff_distance)
            non_bonded_force.setSwitchingDistance(switching_distance)
        else:
            non_bonded_force.setNonbondedMethod(mm.NonbondedForce.NoCutoff)

        self.system.addParticle(mass_1)
        charge_1 = 0.0 * unit.elementary_charge
        non_bonded_force.addParticle(charge_1, sigma_1, epsilon_1)

        self.system.addParticle(mass_2)
        charge_2 = 0.0 * unit.elementary_charge
        non_bonded_force.addParticle(charge_2, sigma_2, epsilon_2)

        _ = self.system.addForce(non_bonded_force)

        # Coordinates

        if coordinates is not None:
            self.set_coordinates(coordinates)

        # Box

        if box is not None:
            self.set_box(box)

        # Potential expresion

        d, eps_r, sigma_r = symbols('d eps_r sigma_r')
        self.potential_expression = 4.0 * eps_r * ((sigma_r / d)**12 -
                                                   (sigma_r / d)**6)
        del (d, eps_r, sigma_r)
Ejemplo n.º 22
0
mass_1 = 39.948 * unit.amu
sigma_1 = 3.404 * unit.angstroms
epsilon_1 = 0.238 * unit.kilocalories_per_mole
charge_1 = 0.0 * unit.elementary_charge

## Segundo átomo: Xenón
mass_2 = 131.293 * unit.amu
sigma_2 = 3.961 * unit.angstroms
epsilon_2 = 0.459 * unit.kilocalories_per_mole
charge_2 = 0.0 * unit.elementary_charge

# Creación del sistema.

system = mm.System()

non_bonded_force = mm.NonbondedForce()
non_bonded_force.setNonbondedMethod(mm.NonbondedForce.NoCutoff)

# Átomo 1
system.addParticle(mass_1)
non_bonded_force.addParticle(charge_1, sigma_1, epsilon_1)

# Átomo 2
system.addParticle(mass_2)
non_bonded_force.addParticle(charge_2, sigma_2, epsilon_2)

# Caja periódica

system.setDefaultPeriodicBoxVectors([2.0, 0.0, 0.0] * unit.nanometers,
                                    [0.0, 2.0, 0.0] * unit.nanometers,
                                    [0.0, 0.0, 2.0] * unit.nanometers)
Ejemplo n.º 23
0
def test_add_dummy_atoms(tmp_path, dummy_complex):

    import mdtraj
    from simtk import openmm
    from simtk import unit as simtk_unit

    # Create an empty system to add the dummy atoms to.
    system_path = os.path.join(tmp_path, "input.xml")

    system = openmm.System()
    system.addForce(openmm.NonbondedForce())

    with open(system_path, "w") as file:
        file.write(openmm.XmlSerializer.serialize(system))

    protocol = AddDummyAtoms("release_add_dummy_atoms")
    protocol.substance = dummy_complex
    protocol.input_coordinate_path = get_data_filename(
        os.path.join("test", "molecules", "methanol_methane.pdb")
    )
    protocol.input_system = ParameterizedSystem(
        substance=dummy_complex,
        force_field=None,
        topology_path=get_data_filename(
            os.path.join("test", "molecules", "methanol_methane.pdb")
        ),
        system_path=system_path,
    )
    protocol.offset = 6.0 * unit.angstrom
    protocol.execute(str(tmp_path))

    # Validate that dummy atoms have been added to the configuration file
    # and the structure has been correctly shifted.
    trajectory = mdtraj.load_pdb(protocol.output_coordinate_path)
    assert trajectory.topology.n_atoms == 14

    assert numpy.allclose(trajectory.xyz[0][11:12, :2], 2.5)
    assert numpy.isclose(trajectory.xyz[0][11, 2], 0.62)
    assert numpy.isclose(trajectory.xyz[0][12, 2], 0.32)
    assert numpy.isclose(trajectory.xyz[0][13, 0], 2.5)
    assert numpy.isclose(trajectory.xyz[0][13, 1], 2.72)
    assert numpy.isclose(trajectory.xyz[0][13, 2], 0.1)

    # Validate the atom / residue names.
    all_atoms = [*trajectory.topology.atoms]
    dummy_atoms = all_atoms[11:14]

    assert all(atom.name == "DUM" for atom in dummy_atoms)
    assert all(dummy_atoms[i].residue.name == f"DM{i + 1}" for i in range(3))

    # Validate that the dummy atoms got added to the system
    with open(protocol.output_system.system_path) as file:
        system: openmm.System = openmm.XmlSerializer.deserialize(file.read())

    assert system.getNumParticles() == 3
    assert all(
        numpy.isclose(system.getParticleMass(i).value_in_unit(simtk_unit.dalton), 207.0)
        for i in range(3)
    )

    assert system.getNumForces() == 1
    assert system.getForce(0).getNumParticles() == 3
Ejemplo n.º 24
0
    def _addNonbondedForceToSystem(self, sys, OPLS):
        """Create the nonbonded force
        """
        cnb = None
        nb = mm.NonbondedForce()
        sys.addForce(nb)

        if OPLS:
            cnb = mm.CustomNonbondedForce(
                "4.0*epsilon12*((sigma12/r)^12 - (sigma12/r)^6); sigma12=sqrt(sigma1*sigma2); epsilon12=sqrt(epsilon1*epsilon2)"
            )
            cnb.addPerParticleParameter("sigma")
            cnb.addPerParticleParameter("epsilon")
            sys.addForce(cnb)

        if OPLS:
            q = """SELECT sigma, epsilon
            FROM particle INNER JOIN nonbonded_param
            ON particle.nbtype=nonbonded_param.id ORDER BY particle.id"""
            for (fcounter, conn, tables, offset) in self._localVars():
                for sigma, epsilon in conn.execute(q):
                    cnb.addParticle(
                        [sigma * angstrom, epsilon * kilocalorie_per_mole])

        q = """SELECT charge, sigma, epsilon
        FROM particle INNER JOIN nonbonded_param
        ON particle.nbtype=nonbonded_param.id ORDER BY particle.id"""
        for (fcounter, conn, tables, offset) in self._localVars():
            for charge, sigma, epsilon in conn.execute(q):
                if OPLS:
                    epsilon = 0
                nb.addParticle(charge, sigma * angstrom,
                               epsilon * kilocalorie_per_mole)

        for (fcounter, conn, tables, offset) in self._localVars():
            for p0, p1 in conn.execute('SELECT p0, p1 FROM exclusion'):
                p0 += offset
                p1 += offset
                nb.addException(p0, p1, 0.0, 1.0, 0.0)
                if OPLS:
                    cnb.addExclusion(p0, p1)

        q = """SELECT p0, p1, aij, bij, qij
        FROM pair_12_6_es_term INNER JOIN pair_12_6_es_param
        ON pair_12_6_es_term.param=pair_12_6_es_param.id"""
        for (fcounter, conn, tables, offset) in self._localVars():
            for p0, p1, a_ij, b_ij, q_ij in conn.execute(q):
                p0 += offset
                p1 += offset
                a_ij = (a_ij * kilocalorie_per_mole *
                        (angstrom**12)).in_units_of(kilojoule_per_mole *
                                                    (nanometer**12))
                b_ij = (b_ij * kilocalorie_per_mole *
                        (angstrom**6)).in_units_of(kilojoule_per_mole *
                                                   (nanometer**6))
                q_ij = q_ij * elementary_charge**2
                if (b_ij._value == 0.0) or (a_ij._value == 0.0):
                    new_epsilon = 0
                    new_sigma = 1
                else:
                    new_epsilon = b_ij**2 / (4 * a_ij)
                    new_sigma = (a_ij / b_ij)**(1.0 / 6.0)
                nb.addException(p0, p1, q_ij, new_sigma, new_epsilon, True)

            n_total = conn.execute(
                """SELECT COUNT(*) FROM pair_12_6_es_term""").fetchone()
            n_in_exclusions = conn.execute("""SELECT COUNT(*)
            FROM exclusion INNER JOIN pair_12_6_es_term
            ON (    ( exclusion.p0==pair_12_6_es_term.p0 AND exclusion.p1==pair_12_6_es_term.p1)
                 OR ( exclusion.p0==pair_12_6_es_term.p1 AND exclusion.p1==pair_12_6_es_term.p0) 
               )""").fetchone()
            if not n_total == n_in_exclusions:
                raise NotImplementedError(
                    'All pair_12_6_es_terms must have a corresponding exclusion'
                )

        return nb, cnb
Ejemplo n.º 25
0
def add_force(cgmodel, force_type=None):
    """

        Given a 'cgmodel' and 'force_type' as input, this function adds
        the OpenMM force corresponding to 'force_type' to 'cgmodel.system'.

        :param cgmodel: CGModel() class object.
        :param type: class

        :param force_type: Designates the kind of 'force' provided. (Valid options include: "Bond", "Nonbonded", "Angle", and "Torsion")
        :type force_type: str

        :returns: 
             - cgmodel (class) - 'foldamers' CGModel() class object
             - force (class) - An OpenMM `Force() <https://simtk.org/api_docs/openmm/api4_1/python/classsimtk_1_1openmm_1_1openmm_1_1Force.html>`_ object.

        :Example:

        >>> from foldamers.cg_model.cgmodel import CGModel
        >>> cgmodel = CGModel()
        >>> force_type = "Bond"
        >>> cgmodel,force = add_force(cgmodel,force_type=force_type)

        """
    if force_type == "Bond":

        bond_force = mm.HarmonicBondForce()
        bond_list = []

        for bond_indices in cgmodel.get_bond_list():
            bond_list.append([bond_indices[0], bond_indices[1]])
            bond_force_constant = cgmodel.get_bond_force_constant(
                bond_indices[0], bond_indices[1])
            bond_length = cgmodel.get_bond_length(bond_indices[0],
                                                  bond_indices[1])
            if cgmodel.constrain_bonds:
                cgmodel.system.addConstraint(bond_indices[0], bond_indices[1],
                                             bond_length)
            bond_length = bond_length.in_units_of(unit.nanometer)._value
            bond_force.addBond(bond_indices[0], bond_indices[1], bond_length,
                               bond_force_constant)

        if len(bond_list) != bond_force.getNumBonds():
            print(
                "ERROR: The number of bonds in the coarse grained model is different\n"
            )
            print("from the number of bonds in its OpenMM System object\n")
            print("There are " + str(len(bond_list)) +
                  " bonds in the coarse grained model\n")
            print("and " + str(bond_force.getNumBonds()) +
                  " bonds in the OpenMM system object.")
            exit()

        cgmodel.system.addForce(bond_force)
        force = bond_force

    if force_type == "Nonbonded":

        nonbonded_force = mm.NonbondedForce()
        nonbonded_force.setNonbondedMethod(mm.NonbondedForce.NoCutoff)

        for particle in range(cgmodel.num_beads):
            charge = cgmodel.get_particle_charge(particle)
            sigma = cgmodel.get_sigma(particle)
            epsilon = cgmodel.get_epsilon(particle)
            nonbonded_force.addParticle(charge, sigma, epsilon)

        if len(cgmodel.bond_list) >= 1:
            nonbonded_force.createExceptionsFromBonds(cgmodel.bond_list, 1.0,
                                                      1.0)
        cgmodel.system.addForce(nonbonded_force)
        force = nonbonded_force
        #for particle in range(cgmodel.num_beads):
        #print(force.getParticleParameters(particle))

    if force_type == "Angle":
        angle_force = mm.HarmonicAngleForce()
        for angle in cgmodel.bond_angle_list:
            bond_angle_force_constant = cgmodel.get_bond_angle_force_constant(
                angle[0], angle[1], angle[2])
            equil_bond_angle = cgmodel.get_equil_bond_angle(
                angle[0], angle[1], angle[2])
            angle_force.addAngle(angle[0], angle[1], angle[2],
                                 equil_bond_angle, bond_angle_force_constant)
        cgmodel.system.addForce(angle_force)
        force = angle_force

    if force_type == "Torsion":
        torsion_force = mm.PeriodicTorsionForce()
        for torsion in cgmodel.torsion_list:
            torsion_force_constant = cgmodel.get_torsion_force_constant(
                torsion)
            equil_torsion_angle = cgmodel.get_equil_torsion_angle(torsion)
            periodicity = 0
            #print(torsion)
            #print(equil_torsion_angle)
            #print(torsion_force_constant)
            torsion_force.addTorsion(torsion[0], torsion[1], torsion[2],
                                     torsion[3], periodicity,
                                     equil_torsion_angle,
                                     torsion_force_constant)
            #print(torsion_force.getNumTorsions())
        cgmodel.system.addForce(torsion_force)
        force = torsion_force

    return (cgmodel, force)
Ejemplo n.º 26
0
def distributeLipids(boxsize,
                     resnames,
                     sigmas,
                     cutoff,
                     mass=39.9 * unit.amu,  # argon
                     epsilon=0.238 * unit.kilocalories_per_mole,  # argon,
                     switch_width=3.4 * unit.angstrom,  # argon
                     ):
        nparticles = len(resnames)
                
        # Determine Lennard-Jones cutoff.
        cutoff = cutoff * unit.angstrom

        cutoff_type = openmm.NonbondedForce.CutoffPeriodic

        # Create an empty system object.
        system = openmm.System()

        # Periodic box vectors.
        a = unit.Quantity((boxsize[0] * unit.angstrom, 0 * unit.angstrom, 0 * unit.angstrom))
        b = unit.Quantity((0 * unit.angstrom, boxsize[1] * unit.angstrom, 0 * unit.angstrom))
        c = unit.Quantity((0 * unit.angstrom, 0 * unit.angstrom, boxsize[2] * unit.angstrom))
        system.setDefaultPeriodicBoxVectors(a, b, c)

        # Set up periodic nonbonded interactions with a cutoff.
        nb = openmm.NonbondedForce()
        nb.setNonbondedMethod(cutoff_type)
        nb.setCutoffDistance(cutoff)
        nb.setUseDispersionCorrection(True)

        nb.setUseSwitchingFunction(False)
        if (switch_width is not None):
            nb.setUseSwitchingFunction(True)
            nb.setSwitchingDistance(cutoff - switch_width)

        for s in sigmas:
            system.addParticle(mass)
            nb.addParticle(0.0 * unit.elementary_charge, s * unit.angstrom, epsilon)

        positions = subrandom_particle_positions(nparticles, system.getDefaultPeriodicBoxVectors(), 2)

        # Add the nonbonded force.
        system.addForce(nb)

        # Add a restraining potential to keep atoms in z=0
        energy_expression = 'k * (z^2)'
        force = openmm.CustomExternalForce(energy_expression)
        force.addGlobalParameter('k', 10)
        for particle_index in range(nparticles):
            force.addParticle(particle_index, [])
        system.addForce(force)

        # Create topology.
        topology = app.Topology()
        chain = topology.addChain()
        elems = ['Ar', 'Cl', 'Na']
        _, idx = np.unique(resnames, return_inverse=True)
        for i in idx:
            element = app.Element.getBySymbol(elems[i])
            residue = topology.addResidue(elems[i], chain)
            topology.addAtom(elems[i], element, residue)

        topology.setUnitCellDimensions(unit.Quantity(boxsize, unit.angstrom)) 
            
        # Simulate it
        from simtk.openmm import LangevinIntegrator, VerletIntegrator
        from simtk.openmm.app import Simulation, PDBReporter, StateDataReporter, PDBFile
        from simtk.unit import kelvin, picoseconds, picosecond, angstrom
        from sys import stdout
        from mdtraj.reporters import DCDReporter
        nsteps = 10000
        freq = 1

        integrator = VerletIntegrator(0.002 * picoseconds)
        simulation = Simulation(topology, system, integrator)
        simulation.context.setPositions(positions)
        simulation.minimizeEnergy()
        # simulation.reporters.append(DCDReporter('output.dcd', 1))
        # simulation.reporters.append(StateDataReporter(stdout, 1000, potentialEnergy=True, totalEnergy=True, step=True, separator='   '))
        simulation.step(nsteps)

        state = simulation.context.getState(getPositions=True, enforcePeriodicBox=True)
        allfinalpos = state.getPositions(asNumpy=True).value_in_unit(angstrom)

        # with open('topology.pdb', 'w') as f:
        #     PDBFile.writeFile(topology, positions, f)

        # from htmd.molecule.molecule import Molecule
        # mol = Molecule('topology.pdb')
        # mol.read('output.dcd')

        return allfinalpos
import scipy.integrate
import simtk.openmm as mm
import pandas as pd

box = 4.0
cutoff = box / 2.
q = 0.1
epsilon = 0.0

system = mm.System()
system.setDefaultPeriodicBoxVectors((box, 0, 0), (0, box, 0), (0, 0, box))
system.addParticle(1.0)
system.addParticle(1.0)

f = mm.NonbondedForce()
f.setCutoffDistance(cutoff)
f.setNonbondedMethod(mm.NonbondedForce.CutoffPeriodic)

f.setUseDispersionCorrection(False)
f.setUseSwitchingFunction(False)
f.setSwitchingDistance(0.9 * cutoff)

f.addParticle(q, 1.0, epsilon)
f.addParticle(-q, 1.0, epsilon)
system.addForce(f)

n = 1000
dt = (box) / n
integrator = mm.CustomIntegrator(dt)

integrator.addPerDofVariable("xcur", 0.0)
Ejemplo n.º 28
0
    def createSystem(self, nonbondedMethod=ff.NoCutoff, nonbondedCutoff=1.0*unit.nanometer,
                     constraints=None, rigidWater=True, implicitSolvent=None, soluteDielectric=1.0, solventDielectric=78.5, ewaldErrorTolerance=0.0005, removeCMMotion=True, hydrogenMass=None):
        """Construct an OpenMM System representing the topology described by this
        prmtop file.

        Parameters
        ----------
        nonbondedMethod : object=NoCutoff
            The method to use for nonbonded interactions.  Allowed values are
            NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, PME, or LJPME.
        nonbondedCutoff : distance=1*nanometer
            The cutoff distance to use for nonbonded interactions
        constraints : object=None
            Specifies which bonds and angles should be implemented with
            constraints. Allowed values are None, HBonds, AllBonds, or HAngles.
        rigidWater : boolean=True
            If true, water molecules will be fully rigid regardless of the value
            passed for the constraints argument
        implicitSolvent : object=None
            If not None, the implicit solvent model to use.  The only allowed
            value is OBC2.
        soluteDielectric : float=1.0
            The solute dielectric constant to use in the implicit solvent model.
        solventDielectric : float=78.5
            The solvent dielectric constant to use in the implicit solvent
            model.
        ewaldErrorTolerance : float=0.0005
            The error tolerance to use if nonbondedMethod is Ewald, PME, or LJPME.
        removeCMMotion : boolean=True
            If true, a CMMotionRemover will be added to the System
        hydrogenMass : mass=None
            The mass to use for hydrogen atoms bound to heavy atoms.  Any mass
            added to a hydrogen is subtracted from the heavy atom to keep their
            total mass the same.

        Returns
        -------
        System
             the newly created System
        """
        # Create the System.

        sys = mm.System()
        boxVectors = self.topology.getPeriodicBoxVectors()
        if boxVectors is not None:
            sys.setDefaultPeriodicBoxVectors(*boxVectors)
        elif nonbondedMethod in (ff.CutoffPeriodic, ff.Ewald, ff.PME, ff.LJPME):
            raise ValueError('Illegal nonbonded method for a non-periodic system')
        nb = mm.NonbondedForce()
        sys.addForce(nb)
        if implicitSolvent is OBC2:
            gb = mm.GBSAOBCForce()
            gb.setSoluteDielectric(soluteDielectric)
            gb.setSolventDielectric(solventDielectric)
            sys.addForce(gb)
            nb.setReactionFieldDielectric(1.0)
        elif implicitSolvent is not None:
            raise ValueError('Illegal value for implicitSolvent')
        bonds = None
        angles = None
        periodic = None
        rb = None
        harmonicTorsion = None
        cmap = None
        mapIndices = {}
        bondIndices = []
        topologyAtoms = list(self.topology.atoms())
        exceptions = []
        fudgeQQ = float(self._defaults[4])

        # Build a lookup table to let us process dihedrals more quickly.

        dihedralTypeTable = {}
        for key in self._dihedralTypes:
            if key[1] != 'X' and key[2] != 'X':
                if (key[1], key[2]) not in dihedralTypeTable:
                    dihedralTypeTable[(key[1], key[2])] = []
                dihedralTypeTable[(key[1], key[2])].append(key)
                if (key[2], key[1]) not in dihedralTypeTable:
                    dihedralTypeTable[(key[2], key[1])] = []
                dihedralTypeTable[(key[2], key[1])].append(key)
        wildcardDihedralTypes = []
        for key in self._dihedralTypes:
            if key[1] == 'X' or key[2] == 'X':
                wildcardDihedralTypes.append(key)
                for types in dihedralTypeTable.values():
                    types.append(key)

        # Loop over molecules and create the specified number of each type.

        for moleculeName, moleculeCount in self._molecules:
            moleculeType = self._moleculeTypes[moleculeName]
            for i in range(moleculeCount):

                # Record the types of all atoms.

                baseAtomIndex = sys.getNumParticles()
                atomTypes = [atom[1] for atom in moleculeType.atoms]
                try:
                    bondedTypes = [self._atomTypes[t][1] for t in atomTypes]
                except KeyError as e:
                    raise ValueError('Unknown atom type: ' + e.message)
                bondedTypes = [b if b is not None else a for a, b in zip(atomTypes, bondedTypes)]

                # Add atoms.

                for fields in moleculeType.atoms:
                    if len(fields) >= 8:
                        mass = float(fields[7])
                    else:
                        mass = float(self._atomTypes[fields[1]][3])
                    sys.addParticle(mass)

                # Add bonds.

                atomBonds = [{} for x in range(len(moleculeType.atoms))]
                for fields in moleculeType.bonds:
                    atoms = [int(x)-1 for x in fields[:2]]
                    types = tuple(bondedTypes[i] for i in atoms)
                    if len(fields) >= 5:
                        params = fields[3:5]
                    elif types in self._bondTypes:
                        params = self._bondTypes[types][3:5]
                    elif types[::-1] in self._bondTypes:
                        params = self._bondTypes[types[::-1]][3:5]
                    else:
                        raise ValueError('No parameters specified for bond: '+fields[0]+', '+fields[1])
                    # Decide whether to use a constraint or a bond.
                    useConstraint = False
                    if rigidWater and topologyAtoms[baseAtomIndex+atoms[0]].residue.name == 'HOH':
                        useConstraint = True
                    if constraints in (AllBonds, HAngles):
                        useConstraint = True
                    elif constraints is HBonds:
                        elements = [topologyAtoms[baseAtomIndex+i].element for i in atoms]
                        if elem.hydrogen in elements:
                            useConstraint = True
                    # Add the bond or constraint.
                    length = float(params[0])
                    if useConstraint:
                        sys.addConstraint(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], length)
                    else:
                        if bonds is None:
                            bonds = mm.HarmonicBondForce()
                            sys.addForce(bonds)
                        bonds.addBond(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], length, float(params[1]))
                    # Record information that will be needed for constraining angles.
                    atomBonds[atoms[0]][atoms[1]] = length
                    atomBonds[atoms[1]][atoms[0]] = length

                # Add angles.

                degToRad = math.pi/180
                for fields in moleculeType.angles:
                    atoms = [int(x)-1 for x in fields[:3]]
                    types = tuple(bondedTypes[i] for i in atoms)
                    if len(fields) >= 6:
                        params = fields[4:]
                    elif types in self._angleTypes:
                        params = self._angleTypes[types][4:]
                    elif types[::-1] in self._angleTypes:
                        params = self._angleTypes[types[::-1]][4:]
                    else:
                        raise ValueError('No parameters specified for angle: '+fields[0]+', '+fields[1]+', '+fields[2])
                    # Decide whether to use a constraint or a bond.
                    useConstraint = False
                    if rigidWater and topologyAtoms[baseAtomIndex+atoms[0]].residue.name == 'HOH':
                        useConstraint = True
                    if constraints is HAngles:
                        elements = [topologyAtoms[baseAtomIndex+i].element for i in atoms]
                        if elements[0] == elem.hydrogen and elements[2] == elem.hydrogen:
                            useConstraint = True
                        elif elements[1] == elem.oxygen and (elements[0] == elem.hydrogen or elements[2] == elem.hydrogen):
                            useConstraint = True
                    # Add the bond or constraint.
                    theta = float(params[0])*degToRad
                    if useConstraint:
                        # Compute the distance between atoms and add a constraint
                        if atoms[0] in atomBonds[atoms[1]] and atoms[2] in atomBonds[atoms[1]]:
                            l1 = atomBonds[atoms[1]][atoms[0]]
                            l2 = atomBonds[atoms[1]][atoms[2]]
                            length = math.sqrt(l1*l1 + l2*l2 - 2*l1*l2*math.cos(theta))
                            sys.addConstraint(baseAtomIndex+atoms[0], baseAtomIndex+atoms[2], length)
                    else:
                        if angles is None:
                            angles = mm.HarmonicAngleForce()
                            sys.addForce(angles)
                        angles.addAngle(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], theta, float(params[1]))
                        if fields[3] == '5':
                            # This is a Urey-Bradley term, so add the bond.
                            if bonds is None:
                                bonds = mm.HarmonicBondForce()
                                sys.addForce(bonds)
                            k = float(params[3])
                            if k != 0:
                                bonds.addBond(baseAtomIndex+atoms[0], baseAtomIndex+atoms[2], float(params[2]), k)

                # Add torsions.

                for fields in moleculeType.dihedrals:
                    atoms = [int(x)-1 for x in fields[:4]]
                    types = tuple(bondedTypes[i] for i in atoms)
                    dihedralType = fields[4]
                    reversedTypes = types[::-1]+(dihedralType,)
                    types = types+(dihedralType,)
                    if (dihedralType in ('1', '2', '4', '9') and len(fields) > 7) or (dihedralType == '3' and len(fields) > 10):
                        paramsList = [fields]
                    else:
                        # Look for a matching dihedral type.
                        paramsList = None
                        if (types[1], types[2]) in dihedralTypeTable:
                            dihedralTypes = dihedralTypeTable[(types[1], types[2])]
                        else:
                            dihedralTypes = wildcardDihedralTypes
                        for key in dihedralTypes:
                            if all(a == b or a == 'X' for a, b in zip(key, types)) or all(a == b or a == 'X' for a, b in zip(key, reversedTypes)):
                                paramsList = self._dihedralTypes[key]
                                if 'X' not in key:
                                    break
                        if paramsList is None:
                            raise ValueError('No parameters specified for dihedral: '+fields[0]+', '+fields[1]+', '+fields[2]+', '+fields[3])
                    for params in paramsList:
                        if dihedralType in ('1', '4', '9'):
                            # Periodic torsion
                            k = float(params[6])
                            if k != 0:
                                if periodic is None:
                                    periodic = mm.PeriodicTorsionForce()
                                    sys.addForce(periodic)
                                periodic.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], int(float(params[7])), float(params[5])*degToRad, k)
                        elif dihedralType == '2':
                            # Harmonic torsion
                            k = float(params[6])
                            if k != 0:
                                if harmonicTorsion is None:
                                    harmonicTorsion = mm.CustomTorsionForce('0.5*k*(theta-theta0)^2')
                                    harmonicTorsion.addPerTorsionParameter('theta0')
                                    harmonicTorsion.addPerTorsionParameter('k')
                                    sys.addForce(harmonicTorsion)
                                harmonicTorsion.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], (float(params[5])*degToRad, k))
                        else:
                            # RB Torsion
                            c = [float(x) for x in params[5:11]]
                            if any(x != 0 for x in c):
                                if rb is None:
                                    rb = mm.RBTorsionForce()
                                    sys.addForce(rb)
                                rb.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], c[0], c[1], c[2], c[3], c[4], c[5])

                # Add CMAP terms.

                for fields in moleculeType.cmaps:
                    atoms = [int(x)-1 for x in fields[:5]]
                    types = tuple(bondedTypes[i] for i in atoms)
                    if len(fields) >= 8 and len(fields) >= 8+int(fields[6])*int(fields[7]):
                        params = fields
                    elif types in self._cmapTypes:
                        params = self._cmapTypes[types]
                    elif types[::-1] in self._cmapTypes:
                        params = self._cmapTypes[types[::-1]]
                    else:
                        raise ValueError('No parameters specified for cmap: '+fields[0]+', '+fields[1]+', '+fields[2]+', '+fields[3]+', '+fields[4])
                    if cmap is None:
                        cmap = mm.CMAPTorsionForce()
                        sys.addForce(cmap)
                    mapSize = int(params[6])
                    if mapSize != int(params[7]):
                        raise ValueError('Non-square CMAPs are not supported')
                    map = []
                    for i in range(mapSize):
                        for j in range(mapSize):
                            map.append(float(params[8+mapSize*((j+mapSize//2)%mapSize)+((i+mapSize//2)%mapSize)]))
                    map = tuple(map)
                    if map not in mapIndices:
                        mapIndices[map] = cmap.addMap(mapSize, map)
                    cmap.addTorsion(mapIndices[map], baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3],
                                 baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], baseAtomIndex+atoms[4])

                # Set nonbonded parameters for particles.

                for fields in moleculeType.atoms:
                    params = self._atomTypes[fields[1]]
                    if len(fields) > 6:
                        q = float(fields[6])
                    else:
                        q = float(params[4])
                    nb.addParticle(q, float(params[6]), float(params[7]))
                    if implicitSolvent is OBC2:
                        if fields[1] not in self._implicitTypes:
                            raise ValueError('No implicit solvent parameters specified for atom type: '+fields[1])
                        gbparams = self._implicitTypes[fields[1]]
                        gb.addParticle(q, float(gbparams[4]), float(gbparams[5]))
                for fields in moleculeType.bonds:
                    atoms = [int(x)-1 for x in fields[:2]]
                    bondIndices.append((baseAtomIndex+atoms[0], baseAtomIndex+atoms[1]))

                # Record nonbonded exceptions.

                for fields in moleculeType.pairs:
                    atoms = [int(x)-1 for x in fields[:2]]
                    types = tuple(atomTypes[i] for i in atoms)
                    if len(fields) >= 5:
                        params = fields[3:5]
                    elif types in self._pairTypes:
                        params = self._pairTypes[types][3:5]
                    elif types[::-1] in self._pairTypes:
                        params = self._pairTypes[types[::-1]][3:5]
                    elif not self._genpairs:
                        raise ValueError('No pair parameters defined for atom '
                                         'types %s and gen-pairs is "no"' % types)
                    else:
                        continue # We'll use the automatically generated parameters
                    atom1params = nb.getParticleParameters(baseAtomIndex+atoms[0])
                    atom2params = nb.getParticleParameters(baseAtomIndex+atoms[1])
                    exceptions.append((baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], atom1params[0]*atom2params[0]*fudgeQQ, params[0], params[1]))
                for fields in moleculeType.exclusions:
                    atoms = [int(x)-1 for x in fields]
                    for atom in atoms[1:]:
                        if atom > atoms[0]:
                            exceptions.append((baseAtomIndex+atoms[0], baseAtomIndex+atom, 0, 0, 0))


        # Create nonbonded exceptions.

        nb.createExceptionsFromBonds(bondIndices, fudgeQQ, float(self._defaults[3]))
        for exception in exceptions:
            nb.addException(exception[0], exception[1], exception[2], float(exception[3]), float(exception[4]), True)

        # Finish configuring the NonbondedForce.

        methodMap = {ff.NoCutoff:mm.NonbondedForce.NoCutoff,
                     ff.CutoffNonPeriodic:mm.NonbondedForce.CutoffNonPeriodic,
                     ff.CutoffPeriodic:mm.NonbondedForce.CutoffPeriodic,
                     ff.Ewald:mm.NonbondedForce.Ewald,
                     ff.PME:mm.NonbondedForce.PME,
                     ff.LJPME:mm.NonbondedForce.LJPME}
        nb.setNonbondedMethod(methodMap[nonbondedMethod])
        nb.setCutoffDistance(nonbondedCutoff)
        nb.setEwaldErrorTolerance(ewaldErrorTolerance)

        # Adjust masses.

        if hydrogenMass is not None:
            for atom1, atom2 in self.topology.bonds():
                if atom1.element == elem.hydrogen:
                    (atom1, atom2) = (atom2, atom1)
                if atom2.element == elem.hydrogen and atom1.element not in (elem.hydrogen, None):
                    transferMass = hydrogenMass-sys.getParticleMass(atom2.index)
                    sys.setParticleMass(atom2.index, hydrogenMass)
                    sys.setParticleMass(atom1.index, sys.getParticleMass(atom1.index)-transferMass)

        # Add a CMMotionRemover.

        if removeCMMotion:
            sys.addForce(mm.CMMotionRemover())
        return sys
Ejemplo n.º 29
0
def createOMMSys(my_top, verbose=False):
    """
    Creates an OpenMM system
    Parameters
    ----------
    my_top : chemlib topology object, should also include general system parameters (i.e. temperatures, etc.)
    verbose : bool
        verbosity of output

    Returns
    -------
    system : OpenMM system
    topOMM : OpenMM topology
    topMDtraj : MDtraj topology

    Notes
    -----
    Todo:
    1) topology.BoxL
    """

    if UNITS != "DimensionlessUnits":
        raise ValueError(
            "Danger! OMM export currently only for dimensionless units, but {} detected"
            .format(Units))
    "takes in Sim 'Sys' object and returns an OpenMM System and Topology"
    print("\n=== OMM export currently uses LJ (Dimensionless) Units ===")
    print("mass: {} dalton".format(mass.value_in_unit(unit.dalton)))
    print("epsilon: {}".format(epsilon))
    print("tau: {}".format(tau))

    system_options = parsevalidate.parseSys(
        my_top.system_specs)  #my_top.system_specs["SystemOptions"]
    #try:
    #    parsevalidate.parseSys(system_options)
    #    parsevalidate.parseFF(my_top)

    # --- box size ---
    Lx, Ly, Lz = parsevalidate.parseBox(system_options)

    #===================================
    # Create a System and its Box Size #
    #===================================
    print("\n=== Creating OMM System ===")
    system = openmm.System()

    # Set the periodic box vectors:
    box_edge = [Lx, Ly, Lz]
    box_vectors = np.diag(box_edge) * sigma
    system.setDefaultPeriodicBoxVectors(*box_vectors)

    #==================
    # Create Topology #
    #==================
    #Topology consists of a set of Chains
    #Each Chain contains a set of Residues,
    #and each Residue contains a set of Atoms.
    #We take the topology from the `sim System` object. Currently we do 1 residue per chain, and residue is effectively the Molecule class in sim.
    print("--- Creating Topology ---")
    # --- first get atom types and create dummy elements for openMM ---
    sim_atom_types = [value for value in my_top.atom_types.values()]
    elements = {}
    atom_type_index = {}
    atom_name_map = []
    sim_atom_type_map = {}
    for ia, atom_type in enumerate(sim_atom_types):
        newsymbol = 'Z{}'.format(ia)
        if newsymbol not in app.element.Element._elements_by_symbol:
            elements[atom_type.name] = app.element.Element(
                200 + ia, atom_type.name, newsymbol, atom_type.mass * mass)
        else:
            elements[atom_type.name] = app.element.Element.getBySymbol(
                newsymbol)

        atom_type_index[atom_type.name] = ia
        atom_name_map.append(atom_type.name)
        sim_atom_type_map[atom_type.name] = atom_type

    # --- next get the molecules and bond_list ---
    residues = []
    """#not used
    molecule_atom_list = {}
    molecule_bond_list = {}
    for mname, m in my_top.molecule_types.items():
        molname = m.name
        residues.append(molname)
        #molecule_atom_list[molname] = [my_top.Atoms[ia] for ia in my_top.atoms_in_mol[im]]  #stores names of atoms in molecule
        molecule_atom_list[molname] = [a.name for r in m for a in r]  #stores names of atoms in molecule
        molecule_bond_list[molname] = [ [b[0],b[1]] for b in m.bonds ]
        #molecule_bond_list[molname] = [ [b[0], b[1]] for b in my_top.bonds_in_mol[im] ]    #stores bond indices in molecule
    """
    # --- aggregate stuff, and add atoms to omm system ---
    # Particles are added one at a time
    # Their indices in the System will correspond with their indices in the Force objects we will add later
    atom_list = [a.name for a in my_top.atoms]  #stores atom names
    res_list = [r.name for r in my_top.residues]  #stores residue names
    mol_list = [m.name for m in my_top.molecules]  #stores molecule names
    for a in my_top.atoms:
        system.addParticle(a.mass * mass)
    print("Total number of particles in system: {}".format(
        system.getNumParticles()))

    # --- the actual work of creating the topology ---

    top = app.topology.Topology()
    mdtrajtop = app.topology.Topology(
    )  #so that later can make molecules whole
    constrained_bonds = False
    constraint_lengths = []
    for im, mol in enumerate(my_top.molecules):
        chain = top.addChain()  #Create new chain for each molecule
        # add the atoms
        atoms_in_this_mol = []
        mdt_atoms_in_this_mol = []
        #atomsInThisMol = [my_top.AtomTypes[ my_top.Atoms[ia] ] for ia in my_top.AtomsInThisMol[im]] #list of atom objects
        for ir, r in enumerate(mol):
            res = top.addResidue(
                mol.name, chain
            )  #don't worry about actually specifying residues within a molecule
            mdt_chain = mdtrajtop.addChain(
            )  #Create new chain for each molecule
            mdt_res = mdtrajtop.addResidue(mol.name, mdt_chain)
            for ia, a in enumerate(r):
                el = elements[a.name]
                #if ia > 0:
                #    previous_atom = atom
                atom = top.addAtom(a.name, el,
                                   res)  #reference to openMM atom instance
                mdt_atom = mdtrajtop.addAtom(
                    a.name,
                    mdtraj.element.Element.getByAtomicNumber(
                        atom_type_index[a.name]), mdt_res
                )  #use a dummy element by matching atomic number == cgAtomTypeIndex
                atoms_in_this_mol.append(atom)
                mdt_atoms_in_this_mol.append(mdt_atom)

        # add the bonds
        for bond_site in mol.bonds:  #mol.bonds stores the intramolecular site indices of the bond
            a1 = atoms_in_this_mol[bond_site[
                0]]  #the atoms_in_this_mol has the current, newly added atoms of this residue
            a2 = atoms_in_this_mol[bond_site[1]]
            if verbose:
                print("Adding bond ({},{}), absolute index {},{}".format(
                    bond_site[0], bond_site[1], a1.index, a2.index))
            #top.addBond( atoms_in_this_mol[bond.SType1.AInd], atoms_in_this_mol[bond.SType2.AInd] )
            new_bond = top.addBond(a1, a2)
            mdtrajtop.addBond(
                a1, a2
            )  #don't worry about adding constraint to the mdtraj topology

            if bond_site.rigid:
                constrained_bonds = True
                if verbose:
                    print("Adding rigid constraint for {},{}".format(a1, a2))
                system.addConstraint(
                    a1.index, a2.index,
                    bond.length)  #constraint uses 0-based indexing of atoms
                constraint_lengths.append(bond.length)
            else:
                constraint_lengths.append(0.0)

    print("Total number of constraints: {}".format(system.getNumConstraints()))

    #====================
    ##CREATE FORCEFIELD##
    #====================
    #Currently only allow for some restricted interactions
    #a) harmonic bonds
    #b) Gaussian repulsion
    #c) external force
    #d) LJ

    #TODO:
    #-) spline/tabulated [would then need to implement a library of function evaluations...]
    #-) special bonds treatment (i.e. all fixed bonds, bending and dihedrals)

    #============================
    #create Bonded Interactions #
    #============================
    #Currently only support harmonic bonds, pairwise bonds
    bond_ffs = parsevalidate.parseBond(my_top.system_specs)
    if bond_ffs:  #found bonds
        print("\n---> Found bonded interactions")
        bonded_force = openmm.HarmonicBondForce()
    #Check for bonds

    #Add bonds
    for mol in my_top.molecules:
        for bond in mol.bonds:
            ai = mol.atoms[bond[0]]
            aj = mol.atoms[bond[1]]

            applicable_potentials = []
            for potential in bond_ffs:
                if potential['bond_type'] == 'harmonic':
                    #atype_name1, atype_name2, bond_length, K = potential[1:]
                    atype_name1 = potential['atype1']
                    atype_name2 = potential['atype2']
                    bond_length = potential['length']
                    K = potential['K']
                    #print('{},{},{},{}'.format(ai.name,aj.name,atype_name1,atype_name2))
                    if (ai.name, aj.name) in [(atype_name1, atype_name2),
                                              (atype_name2, atype_name1)]:
                        applicable_potentials.append(potential)
                        temp_label = 'for sites {},{} in mol {}: absolute atom index {},{}'.format(
                            bond[0], bond[1], mol.name, ai.ind, aj.ind)
                        if K == np.inf:
                            bond.length = bond_length
                            if verbose:
                                print(
                                    "adding rigid constraint for {}, overriding harmonic bond"
                                    .format(temp_label))
                            #if bond_site.rigid: #should be True
                            constrained_bonds = True
                            system.addConstraint(
                                ai.ind, aj.ind, bond.length
                            )  #constraint uses 0-based indexing of atoms
                            constraint_lengths.append(bond.length)
                        else:
                            bonded_force.addBond(
                                ai.ind, aj.ind, bond_length * sigma,
                                2.0 * K * epsilon / sigma / sigma)
                            if verbose:
                                print("adding harmonic bond {}(r-{})^2 for {}".
                                      format(K, bond_length, temp_label))
                else:
                    raise ValueError(
                        'Bond type {} has not been implemented in openMM export yet'
                        .format(potential))
            # end for potential in bond_ffs
            if not applicable_potentials and not bond.rigid:
                raise OMMError(
                    "no bonded potential nor constraint found for bond {} in mol {}"
                    .format(bond, mol.name))
    if bond_ffs:
        system.addForce(bonded_force)
    print('')

    #=====================================================
    #create custom nonbonded force: Gaussian + ShortRange#
    #=====================================================
    #--- iterate over all atom pair types ---
    #for aname1 in atomNameMap:
    #    for aname2 in atomNameMap:
    #        AType1 = simAtomTypeMap[aname1]
    #        AType2 = simAtomTypeMap[aname2]
    #        #AType1 = [atype for atype in Sys.World.AtomTypes if atype.Name==aname1]
    #        #AType2 = [atype for atype in Sys.World.AtomTypes if atype.Name==aname2]
    #        print([AType1,AType2])
    #
    #TODO: spline aggregation of potentials
    #
    ag, u0, dist0, rcut, indv_gaussians = parsevalidate.parseGaussian(
        my_top.system_specs)
    if indv_gaussians:
        print("---> Found individual gaussians interactions")
        print(
            "CAUTION, Inefficient if manually layering a lot of interactions!")

        for ii, entry in enumerate(indv_gaussians):
            typ1, typ2, _ag, _u0, cut = entry[1:]
            print('...adding {}'.format(entry))

            B_name = 'B{}'.format(ii)
            kappa_name = 'kappa{}'.format(ii)
            energy_function = 'LJ + Gaussian - CutoffShift;'
            energy_function += 'Gaussian = {B}*exp(-{kappa}*r^2);'.format(
                B=B_name, kappa=kappa_name)
            energy_function += 'LJ = 0;'
            #energy_function += 'LJ = 4*eps(type1,type2)*(rinv12 - rinv6);'
            #energy_function += 'rinv12 = rinv6^2;'
            energy_function += 'CutoffShift = {B}*exp(-{kappa}*({cut})^2);'.format(
                B=B_name, kappa=kappa_name, cut=cut)

            fcnbg = openmm.CustomNonbondedForce(energy_function)
            fcnbg.setCutoffDistance(cut)
            nonbondedMethod = 2
            fcnbg.setNonbondedMethod(nonbondedMethod)  #2 is cutoff periodic

            fcnbg.addGlobalParameter(B_name,
                                     _u0 / (4 * np.pi * _ag * _ag)**1.5)
            fcnbg.addGlobalParameter(kappa_name, 1 / 4 / _ag / _ag)

            typ1_inds = []
            typ2_inds = []
            for atom in top.atoms():
                fcnbg.addParticle()
                if atom.name == typ1:
                    typ1_inds.append(atom.index)
                if atom.name == typ2:
                    typ2_inds.append(atom.index)

            fcnbg.addInteractionGroup(set(typ1_inds), set(typ2_inds))
            print('......added {} interaction groups'.format(
                fcnbg.getNumInteractionGroups()))

            system.addForce(fcnbg)

    if len(ag) != 0 and len(u0) != 0 and len(dist0) != 0 and rcut is not None:
        print("---> Found gaussians interactions matrix")
        nspec = len(my_top.atom_types)
        if ag.shape[0] != nspec:
            raise ValueError("Gaussian Base widths matrix incorrectly sized")
        if u0.shape[0] != nspec:
            raise ValueError(
                "Gaussian Base prefactor matrix incorrectly sized")

        print("... Detected cutoff: {}".format(rcut))
        GlobalCut = rcut
        nonbondedMethod = 2

        print("... u0 matrix:\n{}".format(u0))
        print("... ag matrix:\n{}".format(ag))
        epsmatrix = np.zeros(ag.shape)
        sigmatrix = np.zeros(ag.shape)
        Bmatrix = u0 / (4 * np.pi * ag * ag)**1.5
        kappamatrix = 1 / 4 / ag / ag
        dist0matrix = dist0

        energy_function = 'LJ + Gaussian - CutoffShift;'
        energy_function += 'LJ = 0;'
        #energy_function += 'LJ = 4*eps(type1,type2)*(rinv12 - rinv6);'
        energy_function += 'Gaussian = B(type1,type2)*exp(-kappa(type1,type2)*(r - dist0(type1,type2))^2);'
        #energy_function += 'rinv12 = rinv6^2;'
        #energy_function += 'rinv6 = (sig(type1,type2)/r)^6;'
        energy_function += 'CutoffShift = 4*eps(type1,type2)*( (sig(type1,type2)/{0})^12 - (sig(type1,type2)/{0})^6 ) + B(type1,type2)*exp(-kappa(type1,type2)*({0}-dist0(type1,type2))^2);'.format(
            GlobalCut)

        fcnb = openmm.CustomNonbondedForce(energy_function)
        fcnb.addPerParticleParameter('type')
        fcnb.setCutoffDistance(GlobalCut)
        fcnb.setNonbondedMethod(nonbondedMethod)  #2 is cutoff periodic

        fcnb.addTabulatedFunction(
            'eps',
            openmm.Discrete2DFunction(nspec, nspec,
                                      epsmatrix.ravel(order='F')))
        fcnb.addTabulatedFunction(
            'sig',
            openmm.Discrete2DFunction(nspec, nspec,
                                      sigmatrix.ravel(order='F')))
        fcnb.addTabulatedFunction(
            'B',
            openmm.Discrete2DFunction(nspec, nspec, Bmatrix.ravel(order='F')))
        fcnb.addTabulatedFunction(
            'kappa',
            openmm.Discrete2DFunction(nspec, nspec,
                                      kappamatrix.ravel(order='F')))
        fcnb.addTabulatedFunction(
            'dist0',
            openmm.Discrete2DFunction(nspec, nspec,
                                      dist0matrix.ravel(order='F')))
        for atom in top.atoms():
            fcnb.addParticle([atom_type_index[atom.name]])
        system.addForce(fcnb)

    # === LJ interactions ===
    sig, eps, rcut, shift, long_range = parsevalidate.parseLJ(
        my_top.system_specs)
    if len(sig) > 0:
        print("---> Found LJ interactions matrix")
        nspec = len(my_top.atom_types)
        if sig.shape[0] != nspec:
            raise ValueError("LJ Base sigma matrix incorrectly sized")
        if eps.shape[0] != nspec:
            raise ValueError("LJ Base epsilon matrix incorrectly sized")

        print("... Detected cutoff: {}".format(rcut))
        GlobalCut = rcut
        nonbondedMethod = 2

        print("... sigma matrix:\n{}".format(sig))
        print("... epsilon matrix:\n{}".format(eps))
        epsmatrix = eps
        sigmatrix = sig

        energy_function = 'LJ - {}*CutoffShift;'.format(int(shift))
        energy_function += 'LJ = 4*LJeps(type1,type2)*(rinv12 - rinv6);'
        energy_function += 'rinv12 = rinv6^2;'
        energy_function += 'rinv6 = (LJsig(type1,type2)/r)^6;'
        energy_function += 'CutoffShift = 4*LJeps(type1,type2)*( (LJsig(type1,type2)/{0})^12 - (LJsig(type1,type2)/{0})^6 );'.format(
            GlobalCut)

        fcnbLJ = openmm.CustomNonbondedForce(energy_function)
        fcnbLJ.addPerParticleParameter('type')
        fcnbLJ.setCutoffDistance(GlobalCut)
        fcnbLJ.setNonbondedMethod(nonbondedMethod)  #2 is cutoff periodic
        fcnbLJ.setUseLongRangeCorrection(long_range)

        fcnbLJ.addTabulatedFunction(
            'LJeps',
            openmm.Discrete2DFunction(nspec, nspec,
                                      epsmatrix.ravel(order='F')))
        fcnbLJ.addTabulatedFunction(
            'LJsig',
            openmm.Discrete2DFunction(nspec, nspec,
                                      sigmatrix.ravel(order='F')))
        for atom in top.atoms():
            fcnbLJ.addParticle([atom_type_index[atom.name]])
        system.addForce(fcnbLJ)

    #=====================================
    #--- create the external potential ---
    #=====================================
    uext_ffs = parsevalidate.parseUExt(my_top.system_specs)
    if uext_ffs:
        print("\n---> Found External Force")
    #TODO: possibly allow the period length to be changed
    direction = ['x', 'y', 'z']

    f_exts = []

    for potential in uext_ffs:
        print(potential)
        type_names_in_potential = potential[1]
        Uext = potential[2]
        n_period = potential[3]
        axis = potential[4]
        offset = potential[5]

        if Uext != 0.:
            external = {
                "planeLoc": offset,
                "ax": axis,
                "U": Uext * epsilon.value_in_unit(unit.kilojoule / unit.mole),
                "NPeriod": n_period
            }

            print("...Adding external potential: Uext={}, NPeriod={}, axis={}".
                  format(external["U"], external["NPeriod"], external["ax"]))
            print("......with atom types: {}".format(type_names_in_potential))
            energy_function = 'U*sin(2*{pi}*NPeriod*(r-{r0})/{L}); r={axis};'.format(
                pi=np.pi,
                L=box_edge[external["ax"]],
                r0=external["planeLoc"],
                axis=direction[external["ax"]])
            print('...{}'.format(energy_function))
            f_exts.append(openmm.CustomExternalForce(energy_function))
            f_exts[-1].addGlobalParameter("U", external["U"])
            f_exts[-1].addGlobalParameter("NPeriod", external["NPeriod"])

            for ia, atom in enumerate(top.atoms()):
                if atom.name in type_names_in_potential:
                    print('adding atom {} {} to external force'.format(
                        ia, atom.name))
                    f_exts[-1].addParticle(ia, [])

            system.addForce(f_exts[-1])

    for f in f_exts:
        print("External potential with amplitude U={}, NPeriod={}".format(
            f.getGlobalParameterDefaultValue(0),
            f.getGlobalParameterDefaultValue(1)))

    #================================
    #--- setup the electrostatics ---
    #================================
    lb, ewald_cut, ewald_tolerance, a_born = parsevalidate.parseElec(
        my_top.system_specs)

    has_electrostatics = False
    if lb is not None:
        has_electrostatics = True

    if has_electrostatics and lb > 0.:
        nbfmethod = openmm.NonbondedForce.PME
        print("To implement in OMM, unit charge is now {}".format(q_factor))
        charge_scale = q_factor * lb**0.5

        nbf = openmm.NonbondedForce()
        nbf.setCutoffDistance(ewald_cut)
        nbf.setEwaldErrorTolerance(ewald_tolerance)
        nbf.setNonbondedMethod(nbfmethod)
        nbf.setUseDispersionCorrection(False)
        nbf.setUseSwitchingFunction(False)

        for i, atom in enumerate(my_top.atoms):
            charge = atom.charge * charge_scale  #In dimensionless, EwaldPotential.Coef is typically 1, and usually change relative strength via temperature. But can also scale the coef, which then acts as lB in the unit length
            LJsigma = 1.0
            LJepsilon = 0.0
            nbf.addParticle(charge, LJsigma, LJepsilon)

        system.addForce(nbf)

    if has_electrostatics and lb > 0. and a_born is not None:  #Need explicit analytical portion to cancel out 1/r; tabulation can't capture such a steep potential
        print("Ewald coefficient is: {}".format(lb))
        #print( "Matrix of smearing correction coefficients is:" )
        #print(smPrefactorMatrix)

        nspec = my_top.num_atom_types
        if nspec != len(a_born):
            raise ValueError("a_born has a different length than num_species!")
        a_born_matrix = np.zeros((nspec, nspec))
        for ii in range(nspec):
            for jj in range(nspec):
                a_born_matrix[ii, jj] = np.sqrt(0.5 * a_born[ii]**2. +
                                                0.5 * a_born[ii]**2.)

        energy_function = 'coef*q1*q2 * ( (erf(factor*r) - 1)/r - shift );'
        energy_function += 'shift = (erf(factor*rcut) -1)/rcut;'
        energy_function += 'factor = sqrt({:f})/2/aborn(type1,type2);'.format(
            np.pi)
        energy_function += 'coef = {:f};'.format(lb)
        energy_function += 'rcut = {:f};'.format(ewald_cut)
        fcnb = openmm.CustomNonbondedForce(energy_function)

        fcnb.addPerParticleParameter('type')
        fcnb.addPerParticleParameter('q')
        fcnb.setCutoffDistance(ewald_cut)
        fcnb.setNonbondedMethod(openmm.NonbondedForce.CutoffPeriodic)
        fcnb.addTabulatedFunction(
            'aborn',
            openmm.Discrete2DFunction(nspec, nspec,
                                      a_born_matrix.ravel(order='F')))
        for ia, atom in enumerate(top.atoms()):
            q = my_top.atoms[ia].charge
            if q != 0.0:
                print("atom {} has charge {}".format(ia, q))
            fcnb.addParticle([atom_type_index[atom.name], q])
        system.addForce(fcnb)

    # === Finished! ===
    return system, top, mdtrajtop