示例#1
0
 def __init__(self, force_field='amber03', water=None, box_length=25*unit.angstroms):
     pdb = app.PDBFile(os.path.join(ufedmm.__path__[0], 'data', 'alanine-dipeptide.pdb'))
     if water is None:
         force_field = app.ForceField(f'{force_field}.xml')
         self.topology = pdb.topology
         self.positions = pdb.positions
         L = box_length.value_in_unit(unit.nanometers)
         vectors = [openmm.Vec3(L, 0, 0), openmm.Vec3(0, L, 0), openmm.Vec3(0, 0, L)]
         self.topology.setPeriodicBoxVectors(vectors)
     else:
         force_field = app.ForceField(f'{force_field}.xml', f'{water}.xml')
         modeller = app.Modeller(pdb.topology, pdb.positions)
         modeller.addSolvent(force_field, model=water, boxSize=box_length*openmm.Vec3(1, 1, 1))
         self.topology = modeller.topology
         self.positions = modeller.positions
     self.system = force_field.createSystem(
         self.topology,
         nonbondedMethod=app.NoCutoff if water is None else app.PME,
         constraints=app.HBonds,
         rigidWater=True,
         removeCMMotion=False,
     )
     atoms = [(a.name, a.residue.name) for a in self.topology.atoms()]
     phi_atoms = [('C', 'ACE'), ('N', 'ALA'), ('CA', 'ALA'), ('C', 'ALA')]
     self.phi = openmm.CustomTorsionForce('theta')
     self.phi.addTorsion(*[atoms.index(i) for i in phi_atoms], [])
     psi_atoms = [('N', 'ALA'), ('CA', 'ALA'), ('C', 'ALA'), ('N', 'NME')]
     self.psi = openmm.CustomTorsionForce('theta')
     self.psi.addTorsion(*[atoms.index(i) for i in psi_atoms], [])
def test_try_random_itoc():
    """
    test whether a perturbed four-atom system gives the same internal and cartesian coords when recomputed with `_internal_to_cartesian`
    and `_cartesian_to_internal` as compared to the values output by `_get_internal_from_omm`
    """
    import perses.rjmc.geometry as geometry
    geometry_engine = geometry.FFAllAngleGeometryEngine({'test': 'true'})
    import simtk.openmm as openmm
    integrator = openmm.VerletIntegrator(1.0*unit.femtoseconds)
    sys = openmm.System()
    force = openmm.CustomTorsionForce("theta")
    for i in range(4):
        sys.addParticle(1.0*unit.amu)
    force.addTorsion(0,1,2,3,[])
    sys.addForce(force)
    atom_position = unit.Quantity(np.array([ 0.10557722 ,-1.10424644 ,-1.08578826]), unit=unit.nanometers)
    bond_position = unit.Quantity(np.array([ 0.0765,  0.1  ,  -0.4005]), unit=unit.nanometers)
    angle_position = unit.Quantity(np.array([ 0.0829 , 0.0952 ,-0.2479]) ,unit=unit.nanometers)
    torsion_position = unit.Quantity(np.array([-0.057 ,  0.0951 ,-0.1863] ) ,unit=unit.nanometers)
    for i in range(1000):
        atom_position += unit.Quantity(np.random.normal(size=3), unit=unit.nanometers)
        r, theta, phi = _get_internal_from_omm(atom_position, bond_position, angle_position, torsion_position)
        recomputed_xyz, _ = geometry_engine._internal_to_cartesian(bond_position, angle_position, torsion_position, r, theta, phi)
        new_r, new_theta, new_phi = _get_internal_from_omm(recomputed_xyz,bond_position, angle_position, torsion_position)
        TOLERANCE = 1e-10
        difference = np.linalg.norm(np.array(atom_position/unit.nanometers) - np.array(recomputed_xyz/unit.nanometers))
        assert difference < TOLERANCE, f"the norm of the difference in positions recomputed with original cartesians ({difference}) is greater than tolerance of {TOLERANCE}"
        difference = np.linalg.norm(np.array([r, theta, phi]) - np.array([new_r, new_theta, new_phi]))
        assert difference < TOLERANCE, f"the norm of the difference in internals recomputed with original sphericals ({difference}) is greater than tolerance of {TOLERANCE}"
示例#3
0
def test_harmonic_torsion_kernel():
    kernel = HarmonicTorsionKernel(
        top.positions, [dihedral.id_atoms for dihedral in top.dihedrals],
        [[1 + 0.1 * i, 1 + 0.05 * i] for i in range(top.n_dihedral)])
    phi, energy, forces = kernel.evaluate()
    print()
    print(phi * 180 / np.pi)
    print(energy)
    print(sum(energy))
    print(forces)

    force = mm.CustomTorsionForce('0.5*k*min(2*pi-dtheta, dtheta)^2;'
                                  'dtheta=abs(theta-theta0);'
                                  'pi=3.1415926535')
    force.addPerTorsionParameter('theta0')
    force.addPerTorsionParameter('k')
    force.setForceGroup(4)
    for i, dihedral in enumerate(top.dihedrals):
        force.addTorsion(*dihedral.id_atoms, [1 + 0.1 * i, 2 + 0.1 * i])

    e, f = calc_energy_with_omm(force, top.positions)
    print(e)
    print(f)

    assert pytest.approx(sum(energy), rel=1E-6) == e
    assert pytest.approx(forces, rel=1E-6) == f
示例#4
0
    def _addPeriodicTorsionsToSystem(self, sys, OPLS):
        """Create the torsion terms
        """
        if OPLS:
            periodic = mm.CustomTorsionForce('f * cos(n * theta - phi0)')
            periodic.addPerTorsionParameter('n')
            periodic.addPerTorsionParameter('phi0')
            periodic.addPerTorsionParameter('f')
        else:
            periodic = mm.PeriodicTorsionForce()
        sys.addForce(periodic)

        q = """SELECT p0, p1, p2, p3, phi0, fc0, fc1, fc2, fc3, fc4, fc5, fc6
        FROM dihedral_trig_term INNER JOIN dihedral_trig_param
        ON dihedral_trig_term.param=dihedral_trig_param.id"""

        for (fcounter, conn, tables, offset) in self._localVars():
            for p0, p1, p2, p3, phi0, fc0, fc1, fc2, fc3, fc4, fc5, fc6 in conn.execute(
                    q):
                p0 += offset
                p1 += offset
                p2 += offset
                p3 += offset
                for order, fc in enumerate([fc0, fc1, fc2, fc3, fc4, fc5,
                                            fc6]):
                    if fc == 0:
                        continue
                    if OPLS:
                        periodic.addTorsion(
                            p0, p1, p2, p3,
                            [order, phi0 * degree, fc * kilocalorie_per_mole])
                    else:
                        periodic.addTorsion(p0, p1, p2, p3, order,
                                            phi0 * degree,
                                            fc * kilocalorie_per_mole)
示例#5
0
 def add_harmonic_dihedral_restraints(self,
                                      system,
                                      coords,
                                      k,
                                      hw,
                                      indices_to_restrain,
                                      debug=False):
     print("Adding harmonic dihedral restraints: ")
     if len(indices_to_restrain) != 4:
         print(
             "WARNING: Must be exactly 4 atoms specified for a dihedral restraint.  Skipping ..."
         )
         return system
     theta0 = self.calculate_dihedral(coords,
                                      *indices_to_restrain)  # in radians
     print(theta0)
     expr = "0.5*k*max(0,( min(d_theta,2*pi-d_theta) - hw ))^2;"
     expr += "d_theta = abs(theta - theta0);"
     expr += f"pi = {np.pi:.10f}"
     force = openmm.CustomTorsionForce(expr)
     force.addGlobalParameter("k", k)
     force.addGlobalParameter("hw", hw)
     force.addPerTorsionParameter("theta0")
     force.addTorsion(*indices_to_restrain, [theta0])
     system.addForce(force)
     return system
示例#6
0
def test_try_random_itoc():

    import perses.rjmc.geometry as geometry
    geometry_engine = geometry.FFAllAngleGeometryEngine({'test': 'true'})
    import simtk.openmm as openmm
    integrator = openmm.VerletIntegrator(1.0*unit.femtoseconds)
    sys = openmm.System()
    force = openmm.CustomTorsionForce("theta")
    for i in range(4):
        sys.addParticle(1.0*unit.amu)
    force.addTorsion(0,1,2,3,[])
    sys.addForce(force)
    atom_position = unit.Quantity(np.array([ 0.10557722 ,-1.10424644 ,-1.08578826]), unit=unit.nanometers)
    bond_position = unit.Quantity(np.array([ 0.0765,  0.1  ,  -0.4005]), unit=unit.nanometers)
    angle_position = unit.Quantity(np.array([ 0.0829 , 0.0952 ,-0.2479]) ,unit=unit.nanometers)
    torsion_position = unit.Quantity(np.array([-0.057 ,  0.0951 ,-0.1863] ) ,unit=unit.nanometers)
    for i in range(1000):
        atom_position += unit.Quantity(np.random.normal(size=3), unit=unit.nanometers)
        r, theta, phi = _get_internal_from_omm(atom_position, bond_position, angle_position, torsion_position)
        r = (r/r.unit)*unit.nanometers
        theta = (theta/theta.unit)*unit.radians
        phi = (phi/phi.unit)*unit.radians
        recomputed_xyz, _ = geometry_engine._internal_to_cartesian(bond_position, angle_position, torsion_position, r, theta, phi)
        new_r, new_theta, new_phi = _get_internal_from_omm(recomputed_xyz,bond_position, angle_position, torsion_position)
        crtp = geometry_engine._cartesian_to_internal(recomputed_xyz,bond_position, angle_position, torsion_position)
示例#7
0
def test_openmm_dihedral():
    import perses.rjmc.geometry as geometry
    geometry_engine = geometry.FFAllAngleGeometryEngine({'test': 'true'})
    import simtk.openmm as openmm
    integrator = openmm.VerletIntegrator(1.0*unit.femtoseconds)
    sys = openmm.System()
    force = openmm.CustomTorsionForce("theta")
    for i in range(4):
        sys.addParticle(1.0*unit.amu)
    force.addTorsion(0,1,2,3,[])
    sys.addForce(force)
    atom_position = unit.Quantity(np.array([ 0.10557722 ,-1.10424644 ,-1.08578826]), unit=unit.nanometers)
    bond_position = unit.Quantity(np.array([ 0.0765,  0.1  ,  -0.4005]), unit=unit.nanometers)
    angle_position = unit.Quantity(np.array([ 0.0829 , 0.0952 ,-0.2479]) ,unit=unit.nanometers)
    torsion_position = unit.Quantity(np.array([-0.057 ,  0.0951 ,-0.1863] ) ,unit=unit.nanometers)
    rtp, detJ = geometry_engine._cartesian_to_internal(atom_position, bond_position, angle_position, torsion_position)
    platform = openmm.Platform.getPlatformByName("Reference")
    context = openmm.Context(sys, integrator, platform)
    positions = [atom_position, bond_position, angle_position, torsion_position]
    context.setPositions(positions)
    state = context.getState(getEnergy=True)
    potential = state.getPotentialEnergy()

    #rotate about the torsion:
    n_divisions = 100
    phis = unit.Quantity(np.arange(0, 2.0*np.pi, (2.0*np.pi)/n_divisions), unit=unit.radians)
    omm_phis = np.zeros(n_divisions)
    for i, phi in enumerate(phis):
        xyz_atom1, _ = geometry_engine._internal_to_cartesian(bond_position, angle_position, torsion_position, rtp[0]*unit.nanometers, rtp[1]*unit.radians, phi)
        context.setPositions([xyz_atom1, bond_position, angle_position, torsion_position])
        state = context.getState(getEnergy=True)
        omm_phis[i] = state.getPotentialEnergy()/unit.kilojoule_per_mole

    return 0
示例#8
0
def fix_dihedral(molecule, system, dihedrals):
    """
    Author: Simon Boothroyd

    impose a dihedral angle constraint so that bonds and angles can change during optimization
    """
    mdtraj_trajectory = mdtraj.Trajectory(
        xyz=molecule.conformers[0],
        topology=mdtraj.Topology.from_openmm(molecule.topology.to_openmm()),
    )
    dihedral_angle = mdtraj.compute_dihedrals(mdtraj_trajectory, np.array([dihedrals]))[
        0
    ][0].item()
    dihedral_restraint = openmm.CustomTorsionForce(
        f"k * min(min(abs(theta - theta_0), abs(theta - theta_0 + 2 * "
        f"{np.pi})), abs(theta - theta_0 - 2 * {np.pi}))^2"
    )
    dihedral_restraint.addPerTorsionParameter("k")
    dihedral_restraint.addPerTorsionParameter("theta_0")
    theta_0 = dihedral_angle
    k = 1.0 * unit.kilocalories_per_mole / unit.radian ** 2
    dihedral_restraint.addTorsion(
        dihedrals[0],
        dihedrals[1],
        dihedrals[2],
        dihedrals[3],
        [k, theta_0],
    )
    system.addForce(dihedral_restraint)
示例#9
0
    def _addImproperHarmonicTorsionsToSystem(self, sys):
        """Create the improper harmonic torsion terms
        """
        harmonicTorsion = mm.CustomTorsionForce('k*(theta-theta0)^2')
        harmonicTorsion.addPerTorsionParameter('theta0')
        harmonicTorsion.addPerTorsionParameter('k')

        go = []

        for (fcounter, conn, tables, offset) in self._localVars():
            if not self._hasTable('improper_harm_term', tables):
                go.append(False)
            else:
                go.append(True)

        if any(go):
            sys.addForce(harmonicTorsion)
        else:
            return

        q = """SELECT p0, p1, p2, p3, phi0, fc
        FROM improper_harm_term INNER JOIN improper_harm_param
        ON improper_harm_term.param=improper_harm_param.id"""

        for (fcounter, conn, tables, offset) in self._localVars():
            if not go[fcounter]:
                continue
            for p0, p1, p2, p3, phi0, fc in conn.execute(q):
                p0 += offset
                p1 += offset
                p2 += offset
                p3 += offset
                harmonicTorsion.addTorsion(
                    p0, p1, p2, p3, [phi0 * degree, fc * kilocalorie_per_mole])
示例#10
0
 def __init__(self, forceField='amber14-all', water=None, boxLength=25*unit.angstroms,
              bareSystem=False):
     pdb = app.PDBFile(os.path.join(afed.__path__[0], 'data', 'alanine-dipeptide.pdb'))
     if water is None:
         force_field = app.ForceField(f'{forceField}.xml')
         self._topology = pdb.topology
         self._positions = pdb.positions
     else:
         force_field = app.ForceField(f'{forceField}.xml', f'{water}.xml')
         modeller = app.Modeller(pdb.topology, pdb.positions)
         modeller.addSolvent(force_field, model=water, boxSize=boxLength*openmm.Vec3(1, 1, 1))
         self._topology = modeller.topology
         self._positions = modeller.positions
     self._system = force_field.createSystem(
         self._topology,
         nonbondedMethod=app.NoCutoff if water is None else app.PME,
         constraints=None,
         rigidWater=False,
         removeCMMotion=False,
     )
     if bareSystem:
         return
     atoms = [(a.name, a.residue.name) for a in self._topology.atoms()]
     psi_atoms = [('N', 'ALA'), ('CA', 'ALA'), ('C', 'ALA'), ('N', 'NME')]
     self._psi_angle = openmm.CustomTorsionForce('theta')
     self._psi_angle.addTorsion(*[atoms.index(i) for i in psi_atoms], [])
     phi_atoms = [('C', 'ACE'), ('N', 'ALA'), ('CA', 'ALA'), ('C', 'ALA')]
     self._phi_angle = openmm.CustomTorsionForce('theta')
     self._phi_angle.addTorsion(*[atoms.index(i) for i in phi_atoms], [])
     period = 360*unit.degrees
     self._psi = afed.DrivenCollectiveVariable('psi', self._psi_angle, unit.radians, period)
     self._phi = afed.DrivenCollectiveVariable('phi', self._phi_angle, unit.radians, period)
     value = 180*unit.degrees
     minval = -value
     maxval = value
     T = 1500*unit.kelvin
     mass = 168.0*unit.dalton*(unit.angstroms/unit.radian)**2
     velocity_scale = unit.sqrt(unit.BOLTZMANN_CONSTANT_kB*unit.AVOGADRO_CONSTANT_NA*T/mass)
     self._psi_driver = afed.DriverParameter('psi_s', unit.radians, value, T, velocity_scale,
                                             minval, maxval, periodic=True)
     self._phi_driver = afed.DriverParameter('phi_s', unit.radians, value, T, velocity_scale,
                                             minval, maxval, periodic=True)
     self._driving_force = afed.HarmonicDrivingForce()
     K = 2.78E3*unit.kilocalories_per_mole/unit.radians**2
     self._driving_force.addPair(self._psi, self._psi_driver, K)
     self._driving_force.addPair(self._phi, self._phi_driver, K)
     self._system.addForce(self._driving_force)
示例#11
0
 def AddTorsionForce(self, phi, psi, magnitude):
     torsion_force = openmm.CustomTorsionForce("0.5*k*min(dtheta, 2*pi-dtheta)^2; dtheta = abs(theta-theta0)")
     torsion_force.addPerTorsionParameter("k")
     torsion_force.addPerTorsionParameter("theta0")
     torsion_force.addGlobalParameter("pi",3.141592653589793)
     k = magnitude * kilojoule
     torsion_force.addTorsion(6,8,14,16, [k, psi])
     torsion_force.addTorsion(4, 6, 8, 14, [k, phi])
     self.system.addForce(torsion_force)
    def _addImproperHarmonicTorsionsToSystem(self, sys):
        """Create the improper harmonic torsion terms
        """
        if not self._hasTable('improper_harm_term'):
            return

        harmonicTorsion = mm.CustomTorsionForce('k*(theta-theta0)^2')
        harmonicTorsion.addPerTorsionParameter('theta0')
        harmonicTorsion.addPerTorsionParameter('k')
        sys.addForce(harmonicTorsion)

        q = """SELECT p0, p1, p2, p3, phi0, fc
        FROM improper_harm_term INNER JOIN improper_harm_param
        ON improper_harm_term.param=improper_harm_param.id"""
        for p0, p1, p2, p3, phi0, fc in self._conn.execute(q):
            harmonicTorsion.addTorsion(p0, p1, p2, p3, [phi0*degree, fc*kilocalorie_per_mole])
示例#13
0
def dihedral_angle_cvs(prmtop_file, name, *atom_types):
    selected_dihedrals = set()
    for dihedral in pmd.amber.AmberParm(prmtop_file).dihedrals:
        atoms = [getattr(dihedral, f'atom{i+1}') for i in range(4)]
        if all(a.type == atype for a, atype in zip(atoms, atom_types)):
            selected_dihedrals.add(tuple(a.idx for a in atoms))
    n = len(selected_dihedrals)
    collective_variables = []
    for i, dihedral in enumerate(selected_dihedrals):
        force = openmm.CustomTorsionForce('theta')
        force.addTorsion(*dihedral, [])
        cv_name = name if n == 1 else f'{name}{i+1}'
        cv = ufedmm.CollectiveVariable(cv_name, force)
        collective_variables.append(cv)
        # print(cv_name, dihedral)
    return collective_variables
示例#14
0
def _get_internal_from_omm(atom_coords, bond_coords, angle_coords, torsion_coords):
    import copy
    #master system, will be used for all three
    sys = openmm.System()
    platform = openmm.Platform.getPlatformByName("Reference")
    for i in range(4):
        sys.addParticle(1.0*unit.amu)

    #first, the bond length:
    bond_sys = openmm.System()
    bond_sys.addParticle(1.0*unit.amu)
    bond_sys.addParticle(1.0*unit.amu)
    bond_force = openmm.CustomBondForce("r")
    bond_force.addBond(0, 1, [])
    bond_sys.addForce(bond_force)
    bond_integrator = openmm.VerletIntegrator(1*unit.femtoseconds)
    bond_context = openmm.Context(bond_sys, bond_integrator, platform)
    bond_context.setPositions([atom_coords, bond_coords])
    bond_state = bond_context.getState(getEnergy=True)
    r = bond_state.getPotentialEnergy()
    del bond_sys, bond_context, bond_integrator

    #now, the angle:
    angle_sys = copy.deepcopy(sys)
    angle_force = openmm.CustomAngleForce("theta")
    angle_force.addAngle(0,1,2,[])
    angle_sys.addForce(angle_force)
    angle_integrator = openmm.VerletIntegrator(1*unit.femtoseconds)
    angle_context = openmm.Context(angle_sys, angle_integrator, platform)
    angle_context.setPositions([atom_coords, bond_coords, angle_coords, torsion_coords])
    angle_state = angle_context.getState(getEnergy=True)
    theta = angle_state.getPotentialEnergy()
    del angle_sys, angle_context, angle_integrator

    #finally, the torsion:
    torsion_sys = copy.deepcopy(sys)
    torsion_force = openmm.CustomTorsionForce("theta")
    torsion_force.addTorsion(0,1,2,3,[])
    torsion_sys.addForce(torsion_force)
    torsion_integrator = openmm.VerletIntegrator(1*unit.femtoseconds)
    torsion_context = openmm.Context(torsion_sys, torsion_integrator, platform)
    torsion_context.setPositions([atom_coords, bond_coords, angle_coords, torsion_coords])
    torsion_state = torsion_context.getState(getEnergy=True)
    phi = torsion_state.getPotentialEnergy()
    del torsion_sys, torsion_context, torsion_integrator

    return r, theta, phi
    def _addPeriodicTorsionsToSystem(self, sys, OPLS):
        """Create the torsion terms
        """

        go = []

        for (fcounter,conn,tables,offset) in self._localVars():
            if not self._hasTable('dihedral_trig_term', tables):
                go.append(False)
            else:
                go.append(True)

        if any(go):
            if OPLS:
                periodic = mm.CustomTorsionForce('f * cos(n * theta - phi0)')
                periodic.addPerTorsionParameter('n')
                periodic.addPerTorsionParameter('phi0')
                periodic.addPerTorsionParameter('f')
            else:
                periodic = mm.PeriodicTorsionForce()
            sys.addForce(periodic)
            periodic.setForceGroup(self._bonded_force_group)
        else:
            return
        
        q = """SELECT p0, p1, p2, p3, phi0, fc0, fc1, fc2, fc3, fc4, fc5, fc6
        FROM dihedral_trig_term INNER JOIN dihedral_trig_param
        ON dihedral_trig_term.param=dihedral_trig_param.id"""

        for (fcounter,conn,tables,offset) in self._localVars():
            if not go[fcounter]:
                continue
            for p0, p1, p2, p3, phi0, fc0, fc1, fc2, fc3, fc4, fc5, fc6 in conn.execute(q):
                p0 += offset
                p1 += offset
                p2 += offset
                p3 += offset
                for order, fc in enumerate([fc0, fc1, fc2, fc3, fc4, fc5, fc6]):
                    if fc == 0:
                        continue
                    if OPLS:
                        periodic.addTorsion(p0, p1, p2, p3, [order, phi0*degree, fc*kilocalorie_per_mole])
                    else:
                        periodic.addTorsion(p0, p1, p2, p3, order, phi0*degree, fc*kilocalorie_per_mole)
示例#16
0
    def _add_torsion_force_terms(self):
        core_energy_expression = 'K*(1+cos(periodicity*theta-phase));'
        core_energy_expression += 'K = k*scale_factor;'  # linearly interpolate spring constant
        core_energy_expression += self.scaling_expression()

        # Create the force and add the relevant parameters
        custom_core_force = openmm.CustomTorsionForce(core_energy_expression)
        custom_core_force.addPerTorsionParameter(
            'periodicity')  # molecule1 periodicity
        custom_core_force.addPerTorsionParameter('phase')  # molecule1 phase
        custom_core_force.addPerTorsionParameter(
            'k')  # molecule1 spring constant
        custom_core_force.addPerTorsionParameter('identifier')

        custom_core_force.addGlobalParameter('solute_scale', 1.0)
        custom_core_force.addGlobalParameter('inter_scale', 1.0)

        self._out_system.addForce(custom_core_force)
        self._out_system_forces[
            custom_core_force.__class__.__name__] = custom_core_force
示例#17
0
    def modify_periodic_torsions(self):
        """
        turn the periodic torsions into a custom torsion force

        lambda_protocol :
        - 'lambda_MM_torsions' : 1 -> 0
        - 'lambda_scale' : beta / beta0
        """
        self._alchemical_to_old_torsions = {}
        torsion_expression = 'lambda_MM_torsions * lambda_scale * k * ( 1 + cos( periodicity * theta - phase))'
        custom_torsion_force = openmm.CustomTorsionForce(torsion_expression)

        #add the global params
        custom_torsion_force.addGlobalParameter('lambda_MM_torsions', 1.)
        custom_torsion_force.addGlobalParameter('lambda_scale', 1.)

        #add the pertorsion params
        custom_torsion_force.addPerTorsionParameter('periodicity')
        custom_torsion_force.addPerTorsionParameter('phase')
        custom_torsion_force.addPerTorsionParameter('k')

        #now to iterate over the torsions.
        for idx in range(self._system_forces['PeriodicTorsionForce'].getNumTorsions()):
            p1, p2, p3, p4, periodicity, phase, k = self._system_forces['PeriodicTorsionForce'].getTorsionParameters(idx)
            if self.is_in_alchemical_region({p1,p2,p3,p4}):
                #first thing to do is to zero the force from the `PeriodicTorsionForce`
                self._system_forces['PeriodicTorsionForce'].setTorsionParameters(idx, p1, p2, p3, p4, periodicity, phase, k*0.0)
                self._endstate_system_forces['PeriodicTorsionForce'].setTorsionParameters(idx, p1, p2, p3, p4, periodicity, phase, k*0.0)

                #then add it to the custom torsion force
                custom_torsion_idx = custom_torsion_force.addTorsion(p1, p2, p3, p4, [periodicity, phase, k])

                #add to the alchemical bonds dict for bookkeeping
                self._alchemical_to_old_torsions[custom_torsion_idx] = idx

        #then add the custom bond force to the system
        if self._system_forces['PeriodicTorsionForce'].usesPeriodicBoundaryConditions():
            custom_torsion_force.setUsesPeriodicBoundaryConditions(True)

        self._system.addForce(custom_torsion_force)
示例#18
0
def constrainC5O5(topology,system):
    C5s = []
    O5s = []
    Ps = []
    H5s = []
    for elem in topology.atoms():
        if elem.name == "C5'":
            C5s.append(elem.index)
        elif elem.name == "O5'":
            O5s.append(elem.index)
        elif elem.name == "P":
            Ps.append(elem.index)
        elif elem.name == "C4'":
            H5s.append(elem.index)
        else:
            pass
    force = mm.CustomTorsionForce('-k0*(theta^2)')
    force.setForceGroup(2)
    force.addPerTorsionParameter('k0')
    for i in range(len(Ps)):
        force.addTorsion(H5s[i+1],C5s[i+1],O5s[i+1],Ps[i],[20])
    system.addForce(force)
    print(C5s, O5s, Ps, H5s)
示例#19
0
def test_constrained_torsion_kernel():
    kernel = ConstrainedTorsionKernel(
        top.positions, [dihedral.id_atoms for dihedral in top.dihedrals],
        [[1 + 0.1 * i, 2 + 0.1 * i] for i in range(top.n_dihedral)])
    phi, energy, forces = kernel.evaluate()
    print()
    print(phi * 180 / np.pi)
    print(energy)
    print(sum(energy))
    print(forces)

    force = mm.CustomTorsionForce('k*(1-cos(theta-theta0))')
    force.addPerTorsionParameter('theta0')
    force.addPerTorsionParameter('k')
    force.setForceGroup(4)
    for i, dihedral in enumerate(top.dihedrals):
        force.addTorsion(*dihedral.id_atoms, [1 + 0.1 * i, 2 + 0.1 * i])

    e, f = calc_energy_with_omm(force, top.positions)
    print(e)
    print(f)

    assert pytest.approx(sum(energy), rel=1E-6) == e
    assert pytest.approx(forces, rel=1E-6) == f
示例#20
0
platform = openmm.Platform.getPlatformByName(args.platform)
properties = dict(Precision='mixed') if args.platform == 'CUDA' else dict()

model = afed.AlanineDipeptideModel(forceField='amber96', water=args.water, bareSystem=True)
system, topology, positions = model.getSystem(), model.getTopology(), model.getPositions()

# Split forces into multiple time scales:
respa_loops = [12, 1]  # time steps = 0.25 fs, 3 fs
for force in system.getForces():
    if isinstance(force, openmm.NonbondedForce):
        force.setForceGroup(1)
        force.setReciprocalSpaceForceGroup(1)

# Add driven collective variables (dihedral angles):
atoms = [(a.name, a.residue.name) for a in topology.atoms()]
psi = openmm.CustomTorsionForce('theta')
psi.addTorsion(*[atoms.index(i) for i in psi_atoms], [])
psi = afed.DrivenCollectiveVariable('psi', psi, unit.radians, period=360*unit.degrees)

phi = openmm.CustomTorsionForce('theta')
phi.addTorsion(*[atoms.index(i) for i in phi_atoms], [])
phi = afed.DrivenCollectiveVariable('phi', phi, unit.radians, period=360*unit.degrees)

# Add driver parameters [Ref: Chen et al., JCP 137 (2), art. 024102, 2012]:
T_dihedrals = 1500*unit.kelvin
mass_dihedrals = 168*unit.dalton*(unit.angstroms/unit.radian)**2
K_dihedrals = 2780*unit.kilocalories_per_mole/unit.radians**2
velocity_scale = unit.sqrt(unit.BOLTZMANN_CONSTANT_kB*unit.AVOGADRO_CONSTANT_NA*T_dihedrals/mass_dihedrals)

psi_driver = afed.DriverParameter('psi_s', unit.radians, psi.evaluate(positions), T_dihedrals,
                                  velocity_scale, -180*unit.degrees, 180*unit.degrees, periodic=True)
示例#21
0
    # Add a barostat
    print("Adding barostat...")
    barostat = openmm.MonteCarloBarostat(pressure, temperature)
    reference_system.addForce(barostat)
else:
    # TODO: Update barostat
    pass

# Add umbrella restraint with global variable to control umbrella position
print('umbrella schedule for dihedral defined by atoms %s : %s' % (str(Roux_atoms), str(torsion_umbrella_values)))
print('umbrella schedule for distance defined by atoms %s : %s' % (str(DFG_atoms), str(distance_umbrella_values)))

from numpy import pi
energy_function = '- (torsion_K/2) * cos(min(dtheta, 2*pi-dtheta)); dtheta = abs(theta-torsion_theta0);'
energy_function += 'pi = %f;' % pi
umbrella_force = openmm.CustomTorsionForce(energy_function)
umbrella_force.addGlobalParameter('torsion_K', 0.0)
umbrella_force.addGlobalParameter('torsion_theta0', 0.0)
umbrella_force.addTorsion(*Roux_atoms, [])
torsion_K = kT/angle_sigma**2
system.addForce(umbrella_force)

energy_function = '(distance_K/2) * (r-distance_r0)^2;'
umbrella_force = openmm.CustomBondForce(energy_function)
umbrella_force.addGlobalParameter('distance_K', 0.0)
umbrella_force.addGlobalParameter('distance_r0', 0.0)
umbrella_force.addBond(*DFG_atoms, [])
distance_K = kT/distance_sigma**2
system.addForce(umbrella_force)

# Create thermodynamic states
示例#22
0
def main(args):
    #Get the structure and topology files from the command line
    #ParmEd accepts a wide range of file types (Amber, GROMACS, CHARMM, OpenMM... but not LAMMPS)
    try:
        topFile = args[0]
        strucFile = args[1]
    except IndexError:
        print("Specify topology and structure files from the command line.")
        sys.exit(2)

    print("Using topology file: %s" % topFile)
    print("Using structure file: %s" % strucFile)

    print("\nSetting up system...")

    #Load in the files for initial simulations
    top = pmd.load_file(topFile)
    struc = pmd.load_file(strucFile)

    #Transfer unit cell information to topology object
    top.box = struc.box[:]

    #Set up some global features to use in all simulations
    temperature = 298.15 * u.kelvin

    #Define the platform (i.e. hardware and drivers) to use for running the simulation
    #This can be CUDA, OpenCL, CPU, or Reference
    #CUDA is for NVIDIA GPUs
    #OpenCL is for CPUs or GPUs, but must be used for old CPUs (not SSE4.1 compatible)
    #CPU only allows single precision (CUDA and OpenCL allow single, mixed, or double)
    #Reference is a clear, stable reference for other code development and is very slow, using double precision by default
    platform = mm.Platform.getPlatformByName('CUDA')
    prop = {  #'Threads': '2', #number of threads for CPU - all definitions must be strings (I think)
        'Precision':
        'mixed',  #for CUDA or OpenCL, select the precision (single, mixed, or double)
        'DeviceIndex':
        '0',  #selects which GPUs to use - set this to zero if using CUDA_VISIBLE_DEVICES
        'DeterministicForces':
        'True'  #Makes sure forces with CUDA and PME are deterministic
    }

    #Create the OpenMM system that can be used as a reference
    systemRef = top.createSystem(
        nonbondedMethod=app.
        PME,  #Uses PME for long-range electrostatics, simple cut-off for LJ
        nonbondedCutoff=12.0 *
        u.angstroms,  #Defines cut-off for non-bonded interactions
        rigidWater=True,  #Use rigid water molecules
        constraints=app.HBonds,  #Constrains all bonds involving hydrogens
        flexibleConstraints=
        False,  #Whether to include energies for constrained DOFs
        removeCMMotion=
        True,  #Whether or not to remove COM motion (don't want to if part of system frozen)
    )

    #Set up the integrator to use as a reference
    integratorRef = mm.LangevinIntegrator(
        temperature,  #Temperature for Langevin
        1.0 / u.picoseconds,  #Friction coefficient
        2.0 * u.femtoseconds,  #Integration timestep
    )
    integratorRef.setConstraintTolerance(1.0E-08)

    #Get solute atoms and solute heavy atoms separately
    soluteIndices = []
    for res in top.residues:
        if res.name not in ['OTM', 'CTM', 'STM', 'NTM', 'SOL']:
            for atom in res.atoms:
                soluteIndices.append(atom.idx)

    #JUST for acetic acid, will also need to add lambda states in order to soften torsions around the C-OH bond
    #Will base this on the work of Paluch, et al. 2011
    #First soften the torsions in the coupled state, then turn them on in the decoupled state
    #Defining lambda states here
    #Below will define a custom torsion force so can globally modify dihedrals of interest alchemically
    lambdaVec = np.array(  #electrostatic lambda - 1.0 is fully interacting, 0.0 is non-interacting
        [
            [
                1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 0.75, 0.50, 0.25, 0.00,
                0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
                0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00
            ],
            #LJ lambdas - 1.0 is fully interacting, 0.0 is non-interacting
            [
                1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00,
                0.90, 0.80, 0.70, 0.60, 0.50, 0.40, 0.35, 0.30, 0.25, 0.20,
                0.15, 0.10, 0.05, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00
            ],
            #Torsion lambdas - 1.0 is fully on, softening from there
            [
                1.00, 0.90, 0.70, 0.30, 0.10, 0.01, 0.01, 0.01, 0.01, 0.01,
                0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01,
                0.01, 0.01, 0.01, 0.01, 0.10, 0.30, 0.70, 0.90, 1.00
            ]
        ])

    #We need to add a custom non-bonded force for the solute being alchemically changed
    #Will be helpful to have handle on non-bonded force handling LJ and coulombic interactions
    NBForce = None
    for frc in systemRef.getForces():
        if (isinstance(frc, mm.NonbondedForce)):
            NBForce = frc

    #Turn off dispersion correction since have interface
    NBForce.setUseDispersionCorrection(False)

    forceLabelsRef = getForceLabels(systemRef)

    decompEnergy(systemRef,
                 struc.positions,
                 labels=forceLabelsRef,
                 verbose=True)

    #JUST do the below for acetic acid!
    #Otherwise will modify all torsions involving hydroxyls
    ########################### Starting some torsion modifications ###########################
    TorsionForce = None
    for frc in systemRef.getForces():
        if (isinstance(frc, mm.PeriodicTorsionForce)):
            TorsionForce = frc

    #Create custom torsion for those we want to soften
    alchTorsionFunction = "lambdaTorsion*k*(1+cos(per*theta-theta0))"
    AlchTorsionForce = mm.CustomTorsionForce(alchTorsionFunction)
    AlchTorsionForce.addGlobalParameter('lambdaTorsion', 1.0)
    AlchTorsionForce.addPerTorsionParameter('per')
    AlchTorsionForce.addPerTorsionParameter('theta0')
    AlchTorsionForce.addPerTorsionParameter('k')

    #Now need to find specific dihedrals we want to modify
    for ind in range(TorsionForce.getNumTorsions()):
        p1, p2, p3, p4, periodicity, phase, kval = TorsionForce.getTorsionParameters(
            ind)
        thisAtomNames = [top.atoms[x].name for x in [p1, p2, p3, p4]]
        if ((set([p1, p2, p3, p4]).issubset(soluteIndices))
                and (set(['H', 'O']).issubset(thisAtomNames))):
            AlchTorsionForce.addTorsion(p1, p2, p3, p4,
                                        [periodicity, phase, kval])
            #Don't want to duplicate
            TorsionForce.setTorsionParameters(ind, p1, p2, p3, p4, periodicity,
                                              phase, kval * 0.0)

    #Add custom torsion force to our system
    systemRef.addForce(AlchTorsionForce)
    ########################### Done with torsion modification bit ###########################

    #Separate out alchemical and regular particles using set objects
    alchemicalParticles = set(soluteIndices)
    chemicalParticles = set(range(
        systemRef.getNumParticles())) - alchemicalParticles

    #Define the soft-core function for turning on/off LJ interactions
    #In energy expressions for CustomNonbondedForce, r is a special variable and refers to the distance between particles
    #All other variables must be defined somewhere in the function.
    #The exception are variables like sigma1 and sigma2.
    #It is understood that a parameter will be added called 'sigma' and that the '1' and '2' are to specify the combining rule.
    softCoreFunction = '4.0*lambdaLJ*epsilon*x*(x-1.0); x = (1.0/reff_sterics);'
    softCoreFunction += 'reff_sterics = (0.5*(1.0-lambdaLJ) + ((r/sigma)^6));'
    softCoreFunction += 'sigma=0.5*(sigma1+sigma2); epsilon = sqrt(epsilon1*epsilon2)'
    #Define the system force for this function and its parameters
    SoftCoreForce = mm.CustomNonbondedForce(softCoreFunction)
    SoftCoreForce.addGlobalParameter(
        'lambdaLJ', 1.0
    )  #Throughout, should follow convention that lambdaLJ=1.0 is fully-interacting state
    SoftCoreForce.addPerParticleParameter('sigma')
    SoftCoreForce.addPerParticleParameter('epsilon')

    #Will turn off electrostatics completely in the original non-bonded force
    #In the end-state, only want electrostatics inside the alchemical molecule
    #To do this, just turn ON a custom force as we turn OFF electrostatics in the original force
    ONE_4PI_EPS0 = 138.935456  #in kJ/mol nm/e^2
    soluteCoulFunction = '(1.0-(lambdaQ^2))*ONE_4PI_EPS0*charge/r;'
    soluteCoulFunction += 'ONE_4PI_EPS0 = %.16e;' % (ONE_4PI_EPS0)
    soluteCoulFunction += 'charge = charge1*charge2'
    SoluteCoulForce = mm.CustomNonbondedForce(soluteCoulFunction)
    #Note this lambdaQ will be different than for soft core (it's also named differently, which is CRITICAL)
    #This lambdaQ corresponds to the lambda that scales the charges to zero
    #To turn on this custom force at the same rate, need to multiply by (1.0-lambdaQ**2), which we do
    SoluteCoulForce.addGlobalParameter('lambdaQ', 1.0)
    SoluteCoulForce.addPerParticleParameter('charge')

    #Also create custom force for intramolecular alchemical LJ interactions
    #Could include with electrostatics, but nice to break up
    #We could also do this with a separate NonbondedForce object, but it would be a little more work, actually
    soluteLJFunction = '4.0*epsilon*x*(x-1.0); x = (sigma/r)^6;'
    soluteLJFunction += 'sigma=0.5*(sigma1+sigma2); epsilon=sqrt(epsilon1*epsilon2)'
    SoluteLJForce = mm.CustomNonbondedForce(soluteLJFunction)
    SoluteLJForce.addPerParticleParameter('sigma')
    SoluteLJForce.addPerParticleParameter('epsilon')

    #Loop over all particles and add to custom forces
    #As we go, will also collect full charges on the solute particles
    #AND we will set up the solute-solute interaction forces
    alchemicalCharges = [[0]] * len(soluteIndices)
    for ind in range(systemRef.getNumParticles()):
        #Get current parameters in non-bonded force
        [charge, sigma, epsilon] = NBForce.getParticleParameters(ind)
        #Make sure that sigma is not set to zero! Fine for some ways of writing LJ energy, but NOT OK for soft-core!
        if sigma / u.nanometer == 0.0:
            newsigma = 0.3 * u.nanometer  #This 0.3 is what's used by GROMACS as a default value for sc-sigma
        else:
            newsigma = sigma
        #Add the particle to the soft-core force (do for ALL particles)
        SoftCoreForce.addParticle([newsigma, epsilon])
        #Also add the particle to the solute only forces
        SoluteCoulForce.addParticle([charge])
        SoluteLJForce.addParticle([sigma, epsilon])
        #If the particle is in the alchemical molecule, need to set it's LJ interactions to zero in original force
        if ind in soluteIndices:
            NBForce.setParticleParameters(ind, charge, sigma, epsilon * 0.0)
            #And keep track of full charge so we can scale it right by lambda
            alchemicalCharges[soluteIndices.index(ind)] = charge

    #Now we need to handle exceptions carefully
    for ind in range(NBForce.getNumExceptions()):
        [p1, p2, excCharge, excSig,
         excEps] = NBForce.getExceptionParameters(ind)
        #For consistency, must add exclusions where we have exceptions for custom forces
        SoftCoreForce.addExclusion(p1, p2)
        SoluteCoulForce.addExclusion(p1, p2)
        SoluteLJForce.addExclusion(p1, p2)

    #Only compute interactions between the alchemical and other particles for the soft-core force
    SoftCoreForce.addInteractionGroup(alchemicalParticles, chemicalParticles)

    #And only compute alchemical/alchemical interactions for other custom forces
    SoluteCoulForce.addInteractionGroup(alchemicalParticles,
                                        alchemicalParticles)
    SoluteLJForce.addInteractionGroup(alchemicalParticles, alchemicalParticles)

    #Set other soft-core parameters as needed
    SoftCoreForce.setCutoffDistance(12.0 * u.angstroms)
    SoftCoreForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoftCoreForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoftCoreForce)

    #Set other parameters as needed - note that for the solute force would like to set no cutoff
    #However, OpenMM won't allow a bunch of potentials with cutoffs then one without...
    #So as long as the solute is smaller than the cut-off, won't have any problems!
    SoluteCoulForce.setCutoffDistance(12.0 * u.angstroms)
    SoluteCoulForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoluteCoulForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoluteCoulForce)

    SoluteLJForce.setCutoffDistance(12.0 * u.angstroms)
    SoluteLJForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoluteLJForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoluteLJForce)

    #For the TRAPPE model of octanol, need to constrain ALL bonds, not just hydrogens
    #So loop through bonds and if it's associated with an octonal atom that's not a hydrogen, add constraint
    octHeavyInds = []
    for res in top.residues:
        if res.name == 'SOL':
            for atom in res.atoms:
                if 'H' not in atom.name[0]:
                    octHeavyInds.append(atom.idx)

    print("With only hydrogens constrained have %i constraints." %
          systemRef.getNumConstraints())

    bondForce = None
    for frc in systemRef.getForces():
        if (isinstance(frc, mm.HarmonicBondForce)):
            bondForce = frc
    for k in range(bondForce.getNumBonds()):
        p1, p2, dist, kspring = bondForce.getBondParameters(k)
        if (p1 in octHeavyInds) and (p2 in octHeavyInds):
            systemRef.addConstraint(p1, p2, dist)
            #Turn off the bond potential energy if adding the constraint
            bondForce.setBondParameters(k, p1, p2, dist, 0.0)

    print("With ALL octanol bonds constrained have %i constraints." %
          systemRef.getNumConstraints())

    forceLabelsRef = getForceLabels(systemRef)

    decompEnergy(systemRef, struc.positions, labels=forceLabelsRef)

    #Do NVT simulation
    stateFileNVT, stateNVT = doSimNVT(top,
                                      systemRef,
                                      integratorRef,
                                      platform,
                                      prop,
                                      temperature,
                                      pos=struc.positions)

    #And do NPT simulation using state information from NVT
    stateFileNPT, stateNPT = doSimNPT(top,
                                      systemRef,
                                      integratorRef,
                                      platform,
                                      prop,
                                      temperature,
                                      inBulk=True,
                                      state=stateFileNVT)

    #And do production run in expanded ensemble!
    stateFileProd, stateProd, weightsVec = doSimExpanded(top,
                                                         systemRef,
                                                         integratorRef,
                                                         platform,
                                                         prop,
                                                         temperature,
                                                         0,
                                                         lambdaVec,
                                                         soluteIndices,
                                                         alchemicalCharges,
                                                         inBulk=True,
                                                         state=stateFileNPT,
                                                         nSteps=8500000,
                                                         equilSteps=1500000)
示例#23
0
    def __init__(self,
                 temperature,
                 nbins,
                 store_filename,
                 protocol=None,
                 mm=None):
        """
        Initialize a Hamiltonian exchange simulation object.

        ARGUMENTS

        store_filename (string) - name of NetCDF file to bind to for simulation output and checkpointing

        OPTIONAL ARGUMENTS

        protocol (dict) - Optional protocol to use for specifying simulation protocol as a dict. Provided keywords will be matched to object variables to replace defaults.

        NOTES

        von Mises distribution used for restraints

        http://en.wikipedia.org/wiki/Von_Mises_distribution

        """

        import simtk.pyopenmm.extras.testsystems as testsystems

        # Create reference system and state.
        [system, coordinates] = testsystems.AlanineDipeptideImplicit()
        self.reference_system = system
        self.reference_state = repex.ThermodynamicState(
            system=system, temperature=temperature)

        self.nbins = nbins
        self.kT = (repex.kB * temperature)
        self.beta = 1.0 / self.kT

        self.delta = 360.0 / float(
            nbins) * units.degrees  # bin spacing (angular)
        self.sigma = self.delta / 3.0  # standard deviation (angular)
        self.kappa = (self.sigma / units.radians)**(
            -2)  # kappa parameter (unitless)

        # Create list of thermodynamic states with different bias potentials.
        states = list()
        # Create a state without a biasing potential.
        [system, coordinates] = testsystems.AlanineDipeptideImplicit()
        state = repex.ThermodynamicState(system=system,
                                         temperature=temperature)
        states.append(state)
        # Create states with biasing potentials.
        for phi_index in range(nbins):
            for psi_index in range(nbins):
                print "bin (%d,%d)" % (phi_index, psi_index)
                # Create system.
                [system, coordinates] = testsystems.AlanineDipeptideImplicit()
                # Add biasing potentials.
                phi0 = (float(phi_index) +
                        0.5) * self.delta - 180.0 * units.degrees
                psi0 = (float(psi_index) +
                        0.5) * self.delta - 180.0 * units.degrees
                force = openmm.CustomTorsionForce(
                    '-kT * kappa * cos(theta - theta0)')
                force.addGlobalParameter('kT',
                                         self.kT / units.kilojoules_per_mole)
                force.addPerTorsionParameter('kappa')
                force.addPerTorsionParameter('theta0')
                force.addTorsion(4, 6, 8, 14,
                                 [self.kappa, phi0 / units.radians])
                force.addTorsion(6, 8, 14, 16,
                                 [self.kappa, psi0 / units.radians])
                system.addForce(force)
                # Add state.
                state = repex.ThermodynamicState(system=system,
                                                 temperature=temperature)
                states.append(state)

        # Initialize replica-exchange simlulation.
        ReplicaExchange.__init__(self,
                                 states,
                                 coordinates,
                                 store_filename,
                                 protocol=protocol,
                                 mm=mm)

        # Override title.
        self.title = '2D umbrella sampling replica-exchange simulation created on %s' % time.asctime(
            time.localtime())

        return
示例#24
0
    def add_restraints(self, system):
        # Bond Restraints
        if self.restrain_bondfile is not None:
            nifty.printcool(" Adding bonding restraints!")
            # Harmonic constraint

            flat_bottom_force = openmm.CustomBondForce(
                'step(r-r0) * (k/2) * (r-r0)^2')
            flat_bottom_force.addPerBondParameter('r0')
            flat_bottom_force.addPerBondParameter('k')
            system.addForce(flat_bottom_force)

            with open(self.restrain_bondfile, 'r') as input_file:
                for line in input_file:
                    print(line)
                    columns = line.split()
                    atom_index_i = int(columns[0])
                    atom_index_j = int(columns[1])
                    r0 = float(columns[2])
                    k = float(columns[3])
                    flat_bottom_force.addBond(atom_index_i, atom_index_j,
                                              [r0, k])

        # Torsion restraint
        if self.restrain_torfile is not None:
            nifty.printcool(" Adding torsional restraints!")

            # Harmonic constraint
            tforce = openmm.CustomTorsionForce(
                "0.5*k*min(dtheta, 2*pi-dtheta)^2; dtheta = abs(theta-theta0); pi = 3.1415926535"
            )
            tforce.addPerTorsionParameter("k")
            tforce.addPerTorsionParameter("theta0")
            system.addForce(tforce)

            xyz = manage_xyz.xyz_to_np(self.geom)
            with open(self.restrain_torfile, 'r') as input_file:
                for line in input_file:
                    columns = line.split()
                    a = int(columns[0])
                    b = int(columns[1])
                    c = int(columns[2])
                    d = int(columns[3])
                    k = float(columns[4])
                    dih = Dihedral(a, b, c, d)
                    theta0 = dih.value(xyz)
                    tforce.addTorsion(a, b, c, d, [k, theta0])

        # Translation restraint
        if self.restrain_tranfile is not None:
            nifty.printcool(" Adding translational restraints!")
            trforce = openmm.CustomExternalForce(
                "k*periodicdistance(x, y, z, x0, y0, z0)^2")
            trforce.addPerParticleParameter("k")
            trforce.addPerParticleParameter("x0")
            trforce.addPerParticleParameter("y0")
            trforce.addPerParticleParameter("z0")
            system.addForce(trforce)

            xyz = manage_xyz.xyz_to_np(self.geom)
            with open(self.restrain_tranfile, 'r') as input_file:
                for line in input_file:
                    columns = line.split()
                    a = int(columns[0])
                    k = float(columns[1])
                    x0 = xyz[a, 0] * 0.1  # Units are in nm
                    y0 = xyz[a, 1] * 0.1  # Units are in nm
                    z0 = xyz[a, 2] * 0.1  # Units are in nm
                    trforce.addParticle(a, [k, x0, y0, z0])
示例#25
0
import math

## read CHARMM force field for proteins
charmm_toppar = omm_app.CharmmParameterSet("./data/top_all36_prot.rtf",
                                           "./data/par_all36_prot.prm")

## read psf and pdb file of dialanine
psf = omm_app.CharmmPsfFile("./output/dialanine.psf")
pdb = omm_app.PDBFile('./data/dialanine.pdb')

## create a OpenMM system based on psf of dialanine and CHARMM force field
system = psf.createSystem(charmm_toppar, nonbondedMethod=omm_app.NoCutoff)

## add harmonic biasing potentials on two dihedrals of dialanine (psi, phi) in the OpenMM system
## for dihedral psi
bias_torsion_psi = omm.CustomTorsionForce(
    "0.5*k_psi*dtheta^2; dtheta = min(tmp, 2*pi-tmp); tmp = abs(theta - psi)")
bias_torsion_psi.addGlobalParameter("pi", math.pi)
bias_torsion_psi.addGlobalParameter("k_psi", 1.0)
bias_torsion_psi.addGlobalParameter("psi", 0.0)
## 4, 6, 8, 14 are indices of the atoms of the torsion psi
bias_torsion_psi.addTorsion(4, 6, 8, 14)

## for dihedral phi
bias_torsion_phi = omm.CustomTorsionForce(
    "0.5*k_phi*dtheta^2; dtheta = min(tmp, 2*pi-tmp); tmp = abs(theta - phi)")
bias_torsion_phi.addGlobalParameter("pi", math.pi)
bias_torsion_phi.addGlobalParameter("k_phi", 1.0)
bias_torsion_phi.addGlobalParameter("phi", 0.0)
## 6, 8, 14, 16 are indices of the atoms of the torsion phi
bias_torsion_phi.addTorsion(6, 8, 14, 16)
示例#26
0
def test_openmm_dihedral():
    """
    Test FFAllAngleGeometryEngine _internal_to_cartesian and _cartesian_to_internal are consistent with OpenMM torsion angles.
    """
    TORSION_TOLERANCE = 1.0e-4  # permitted disagreement in torsions

    # Create geometry engine
    from perses.rjmc import geometry
    geometry_engine = geometry.FFAllAngleGeometryEngine({'test': 'true'})

    # Create a four-bead test system with a single custom force that measures the OpenMM torsion
    import simtk.openmm as openmm
    integrator = openmm.VerletIntegrator(1.0 * unit.femtoseconds)
    sys = openmm.System()
    force = openmm.CustomTorsionForce("theta")
    for i in range(4):
        sys.addParticle(1.0 * unit.amu)
    force.addTorsion(0, 1, 2, 3, [])
    sys.addForce(force)
    positions = unit.Quantity(
        np.array([
            [0.10557722, -1.10424644, -1.08578826],
            [0.0765, 0.1, -0.4005],
            [0.0829, 0.0952, -0.2479],
            [-0.057, 0.0951, -0.1863],
        ]), unit.nanometers)
    atom_position = positions[0, :]
    bond_position = positions[1, :]
    angle_position = positions[2, :]
    torsion_position = positions[3, :]

    #atom_position = unit.Quantity(np.array([ 0.10557722 ,-1.10424644 ,-1.08578826]), unit=unit.nanometers)
    #bond_position = unit.Quantity(np.array([ 0.0765,  0.1  ,  -0.4005]), unit=unit.nanometers)
    #angle_position = unit.Quantity(np.array([ 0.0829 , 0.0952 ,-0.2479]) ,unit=unit.nanometers)
    #torsion_position = unit.Quantity(np.array([-0.057 ,  0.0951 ,-0.1863] ) ,unit=unit.nanometers)

    # Compute the dimensionless internal coordinates consistent with this geometry
    rtp, detJ = geometry_engine._cartesian_to_internal(atom_position,
                                                       bond_position,
                                                       angle_position,
                                                       torsion_position)
    (r, theta, phi) = rtp  # dimensionless internal coordinates

    # Create a reference context
    platform = openmm.Platform.getPlatformByName("Reference")
    context = openmm.Context(sys, integrator, platform)
    context.setPositions(
        [atom_position, bond_position, angle_position, torsion_position])
    openmm_phi = context.getState(getEnergy=True).getPotentialEnergy(
    ) / unit.kilojoule_per_mole  # this system converts torsion radians -> kJ/mol
    assert np.linalg.norm(
        openmm_phi - phi
    ) < TORSION_TOLERANCE, '_cartesian_to_internal and OpenMM disagree on torsions'

    # Test _internal_to_cartesian by rotating around the torsion
    n_divisions = 100
    phis = np.arange(
        -np.pi, np.pi, (2.0 * np.pi) / n_divisions
    )  # _internal_to_cartesian only accepts dimensionless quantities
    for i, phi in enumerate(phis):
        # Note that (r, theta, phi) are dimensionless here
        xyz_atom1, _ = geometry_engine._internal_to_cartesian(
            bond_position, angle_position, torsion_position, r, theta, phi)
        positions[0, :] = xyz_atom1
        context.setPositions(positions)
        openmm_phi = context.getState(getEnergy=True).getPotentialEnergy(
        ) / unit.kilojoule_per_mole  # this system converts torsion radians -> kJ/mol
        msg = '_internal_to_cartesian and OpenMM disagree on torsions: \n'
        msg += '_internal_to_cartesian generated positions for: {}\n'.format(
            phi)
        msg += 'OpenMM: {}\n'.format(openmm_phi)
        msg += 'positions: {}'.format(positions)
        assert np.linalg.norm(openmm_phi - phi) < TORSION_TOLERANCE, msg

        # Check that _cartesian_to_internal agrees
        rtp, detJ = geometry_engine._cartesian_to_internal(
            xyz_atom1, bond_position, angle_position, torsion_position)
        assert np.linalg.norm(
            phi - rtp[2]
        ) < TORSION_TOLERANCE, '_internal_to_cartesian disagrees with _cartesian_to_internal'

    # Clean up
    del context
示例#27
0
import os
import numpy as np
from sys import exit

## read psf and pdb file of butane
psf = omm_app.CharmmPsfFile('./data/butane.psf')
pdb = omm_app.PDBFile('./data/butane.pdb')

## read CHARMM force field for butane
params = omm_app.CharmmParameterSet('./data/top_all35_ethers.rtf',
                                    './data/par_all35_ethers.prm')

## create a OpenMM system based on psf of butane and CHARMM force field
system = psf.createSystem(params, nonbondedMethod=omm_app.NoCutoff)

## add a harmonic biasing potential on butane dihedral to the OpenMM system
bias_torsion = omm.CustomTorsionForce(
    "0.5*K*dtheta^2; dtheta = min(diff, 2*Pi-diff); diff = abs(theta - theta0)"
)
bias_torsion.addGlobalParameter("Pi", math.pi)
bias_torsion.addGlobalParameter("K", 1.0)
bias_torsion.addGlobalParameter("theta0", 0.0)
## 3, 6, 9, 13 are indices of the four carton atoms in butane, between which
## the dihedral angle is biased.
bias_torsion.addTorsion(3, 6, 9, 13)
system.addForce(bias_torsion)

## save the OpenMM system of butane
with open("./output/system.xml", 'w') as file_handle:
    file_handle.write(omm.XmlSerializer.serialize(system))
示例#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
def main(args):
    #Get the structure and topology files from the command line
    #ParmEd accepts a wide range of file types (Amber, GROMACS, CHARMM, OpenMM... but not LAMMPS)
    try:
        topFile = args[0]
        strucFile = args[1]
    except IndexError:
        print("Specify topology and structure files from the command line.")
        sys.exit(2)

    print("Using topology file: %s" % topFile)
    print("Using structure file: %s" % strucFile)

    print("\nSetting up system...")

    #Load in the files for initial simulations
    top = pmd.load_file(topFile)
    struc = pmd.load_file(strucFile)

    #Transfer unit cell information to topology object
    top.box = struc.box[:]

    #Set up some global features to use in all simulations
    temperature = 298.15 * u.kelvin

    #Define the platform (i.e. hardware and drivers) to use for running the simulation
    #This can be CUDA, OpenCL, CPU, or Reference
    #CUDA is for NVIDIA GPUs
    #OpenCL is for CPUs or GPUs, but must be used for old CPUs (not SSE4.1 compatible)
    #CPU only allows single precision (CUDA and OpenCL allow single, mixed, or double)
    #Reference is a clear, stable reference for other code development and is very slow, using double precision by default
    platform = mm.Platform.getPlatformByName('CUDA')
    prop = {  #'Threads': '2', #number of threads for CPU - all definitions must be strings (I think)
        'Precision':
        'mixed',  #for CUDA or OpenCL, select the precision (single, mixed, or double)
        'DeviceIndex':
        '0',  #selects which GPUs to use - set this to zero if using CUDA_VISIBLE_DEVICES
        'DeterministicForces':
        'True'  #Makes sure forces with CUDA and PME are deterministic
    }

    #Create the OpenMM system that can be used as a reference
    systemRef = top.createSystem(
        nonbondedMethod=app.
        PME,  #Uses PME for long-range electrostatics, simple cut-off for LJ
        nonbondedCutoff=12.0 *
        u.angstroms,  #Defines cut-off for non-bonded interactions
        rigidWater=True,  #Use rigid water molecules
        constraints=app.HBonds,  #Constrains all bonds involving hydrogens
        flexibleConstraints=
        False,  #Whether to include energies for constrained DOFs
        removeCMMotion=
        False,  #Whether or not to remove COM motion (don't want to if part of system frozen)
    )

    #Set up the integrator to use as a reference
    integratorRef = mm.LangevinIntegrator(
        temperature,  #Temperature for Langevin
        1.0 / u.picoseconds,  #Friction coefficient
        2.0 * u.femtoseconds,  #Integration timestep
    )
    integratorRef.setConstraintTolerance(1.0E-08)

    #To freeze atoms, set mass to zero (does not apply to virtual sites, termed "extra particles" in OpenMM)
    #Here assume (correctly, I think) that the topology indices for atoms correspond to those in the system
    for i, atom in enumerate(top.atoms):
        if atom.type in ('SU'):  #, 'CU', 'CUO'):
            systemRef.setParticleMass(i, 0 * u.dalton)

    #Get solute atoms and solute heavy atoms separately
    #Do this for as many solutes as we have so get list for each
    soluteIndices = []
    heavyIndices = []
    for res in top.residues:
        if res.name not in ['OTM', 'CTM', 'STM', 'NTM', 'SOL']:
            thissolinds = []
            thisheavyinds = []
            for atom in res.atoms:
                thissolinds.append(atom.idx)
                if 'H' not in atom.name[0]:
                    thisheavyinds.append(atom.idx)
            soluteIndices.append(thissolinds)
            heavyIndices.append(thisheavyinds)

    #For convenience, also create flattened version of soluteIndices
    allSoluteIndices = []
    for inds in soluteIndices:
        allSoluteIndices += inds

    #Also get surface SU atoms and surface CU atoms at top and bottom of surface
    surfIndices = []
    for atom in top.atoms:
        if atom.type == 'SU':
            surfIndices.append(atom.idx)

    print("\nSolute indices: %s" % str(soluteIndices))
    print("(all together - %s)" % str(allSoluteIndices))
    print("Solute heavy atom indices: %s" % str(heavyIndices))
    print("Surface SU atom indices: %s" % str(surfIndices))

    #Will now add a custom bonded force between heavy atoms of each solute and surface SU atoms
    #Should be in units of kJ/mol*nm^2, but should check this
    #Also, note that here we are using a flat-bottom restraint to keep close to surface
    #AND to keep from penetrating into surface when it's in the decoupled state
    refZlo = 1.4 * u.nanometer  #in nm, the distance between the SU atoms and the solute centroid
    refZhi = 1.7 * u.nanometer
    restraintExpression = '0.5*k*step(refZlo - (z2 - z1))*(((z2 - z1) - refZlo)^2)'
    restraintExpression += '+ 0.5*k*step((z2 - z1) - refZhi)*(((z2 - z1) - refZhi)^2)'
    restraintForce = mm.CustomCentroidBondForce(2, restraintExpression)
    restraintForce.addPerBondParameter('k')
    restraintForce.addPerBondParameter('refZlo')
    restraintForce.addPerBondParameter('refZhi')
    restraintForce.addGroup(surfIndices, np.ones(
        len(surfIndices)))  #Don't weight with masses
    #To assign flat-bottom restraint correctly, need to know if each solute is above or below interface
    #Will need surface z-positions for this
    suZpos = np.average(struc.coordinates[surfIndices, 2])
    for i in range(len(heavyIndices)):
        restraintForce.addGroup(heavyIndices[i], np.ones(len(heavyIndices[i])))
        solZpos = np.average(struc.coordinates[heavyIndices[i], 2])
        if (solZpos - suZpos) > 0:
            restraintForce.addBond([0, i + 1], [10000.0, refZlo, refZhi])
        else:
            #A little confusing, but have to negate and switch for when z2-z1 is always going to be negative
            restraintForce.addBond([0, i + 1], [10000.0, -refZhi, -refZlo])
    systemRef.addForce(restraintForce)

    #We also will need to force the solute to sample the entire surface
    #We will do this with flat-bottom restraints in x and y
    #But NOT on the centroid, because this is hard with CustomExternalForce
    #Instead, just apply it to the first heavy atom of each solute
    #Won't worry about keeping each solute in the same region of the surface on both faces...
    #Actually better if define regions on both faces differently
    initRefX = 0.25 * top.box[
        0] * u.angstrom  #Used parmed to load, and that program uses angstroms
    initRefY = 0.25 * top.box[1] * u.angstrom
    refDistX = 0.25 * top.box[0] * u.angstrom
    refDistY = 0.25 * top.box[1] * u.angstrom
    print('Default X reference distance: %s' % str(initRefX))
    print('Default Y reference distance: %s' % str(initRefY))
    restraintExpressionXY = '0.5*k*step(periodicdistance(x,0,0,refX,0,0) - distX)*((periodicdistance(x,0,0,refX,0,0) - distX)^2)'
    restraintExpressionXY += '+ 0.5*k*step(periodicdistance(0,y,0,0,refY,0) - distY)*((periodicdistance(0,y,0,0,refY,0) - distY)^2)'
    restraintXYForce = mm.CustomExternalForce(restraintExpressionXY)
    restraintXYForce.addPerParticleParameter('k')
    restraintXYForce.addPerParticleParameter('distX')
    restraintXYForce.addPerParticleParameter('distY')
    restraintXYForce.addGlobalParameter('refX', initRefX)
    restraintXYForce.addGlobalParameter('refY', initRefY)
    for i in range(len(heavyIndices)):
        restraintXYForce.addParticle(
            heavyIndices[i][0],
            [1000.0, refDistX, refDistY])  #k in units kJ/mol*nm^2
    systemRef.addForce(restraintXYForce)

    #JUST for acetic acid, will also need to add lambda states in order to soften torsions around the C-OH bond
    #Will base this on the work of Paluch, et al. 2011
    #First soften the torsions in the coupled state, then turn them on in the decoupled state
    #Defining lambda states here
    #Below will define a custom torsion force so can globally modify dihedrals of interest alchemically
    lambdaVec = np.array(  #electrostatic lambda - 1.0 is fully interacting, 0.0 is non-interacting
        [
            [
                1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 0.75, 0.50, 0.25, 0.00,
                0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,
                0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00
            ],
            #LJ lambdas - 1.0 is fully interacting, 0.0 is non-interacting
            [
                1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00,
                0.90, 0.80, 0.70, 0.60, 0.50, 0.40, 0.35, 0.30, 0.25, 0.20,
                0.15, 0.10, 0.05, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00
            ],
            #Torsion lambdas - 1.0 is fully on, softening from there
            [
                1.00, 0.90, 0.70, 0.30, 0.10, 0.01, 0.01, 0.01, 0.01, 0.01,
                0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01,
                0.01, 0.01, 0.01, 0.01, 0.10, 0.30, 0.70, 0.90, 1.00
            ]
        ])

    #We need to add a custom non-bonded force for the solute being alchemically changed
    #Will be helpful to have handle on non-bonded force handling LJ and coulombic interactions
    NBForce = None
    for frc in systemRef.getForces():
        if (isinstance(frc, mm.NonbondedForce)):
            NBForce = frc

    #Turn off dispersion correction since have interface
    NBForce.setUseDispersionCorrection(False)

    forceLabelsRef = getForceLabels(systemRef)

    decompEnergy(systemRef,
                 struc.positions,
                 labels=forceLabelsRef,
                 verbose=True)

    #JUST do the below for acetic acid!
    #Otherwise will modify all torsions involving hydroxyls
    ########################### Starting some torsion modifications ###########################
    TorsionForce = None
    for frc in systemRef.getForces():
        if (isinstance(frc, mm.PeriodicTorsionForce)):
            TorsionForce = frc

    #Create custom torsion for those we want to soften
    alchTorsionFunction = "lambdaTorsion*k*(1+cos(per*theta-theta0))"
    AlchTorsionForce = mm.CustomTorsionForce(alchTorsionFunction)
    AlchTorsionForce.addGlobalParameter('lambdaTorsion', 1.0)
    AlchTorsionForce.addPerTorsionParameter('per')
    AlchTorsionForce.addPerTorsionParameter('theta0')
    AlchTorsionForce.addPerTorsionParameter('k')

    #Now need to find specific dihedrals we want to modify
    for ind in range(TorsionForce.getNumTorsions()):
        p1, p2, p3, p4, periodicity, phase, kval = TorsionForce.getTorsionParameters(
            ind)
        thisAtomNames = [top.atoms[x].name for x in [p1, p2, p3, p4]]
        if ((set([p1, p2, p3, p4]).issubset(allSoluteIndices))
                and (set(['H', 'O']).issubset(thisAtomNames))):
            AlchTorsionForce.addTorsion(p1, p2, p3, p4,
                                        [periodicity, phase, kval])
            #Don't want to duplicate
            TorsionForce.setTorsionParameters(ind, p1, p2, p3, p4, periodicity,
                                              phase, kval * 0.0)

    #Add custom torsion force to our system
    systemRef.addForce(AlchTorsionForce)
    ########################### Done with torsion modification bit ###########################

    #Separate out alchemical and regular particles using set objects
    alchemicalParticles = set(allSoluteIndices)
    chemicalParticles = set(range(
        systemRef.getNumParticles())) - alchemicalParticles

    #Define the soft-core function for turning on/off LJ interactions
    #In energy expressions for CustomNonbondedForce, r is a special variable and refers to the distance between particles
    #All other variables must be defined somewhere in the function.
    #The exception are variables like sigma1 and sigma2.
    #It is understood that a parameter will be added called 'sigma' and that the '1' and '2' are to specify the combining rule.
    softCoreFunction = '4.0*lambdaLJ*epsilon*x*(x-1.0); x = (1.0/reff_sterics);'
    softCoreFunction += 'reff_sterics = (0.5*(1.0-lambdaLJ) + ((r/sigma)^6));'
    softCoreFunction += 'sigma=0.5*(sigma1+sigma2); epsilon = sqrt(epsilon1*epsilon2)'
    #Define the system force for this function and its parameters
    SoftCoreForce = mm.CustomNonbondedForce(softCoreFunction)
    SoftCoreForce.addGlobalParameter(
        'lambdaLJ', 1.0
    )  #Throughout, should follow convention that lambdaLJ=1.0 is fully-interacting state
    SoftCoreForce.addPerParticleParameter('sigma')
    SoftCoreForce.addPerParticleParameter('epsilon')

    #Will turn off electrostatics completely in the original non-bonded force
    #In the end-state, only want electrostatics inside the alchemical molecule
    #To do this, just turn ON a custom force as we turn OFF electrostatics in the original force
    ONE_4PI_EPS0 = 138.935456  #in kJ/mol nm/e^2
    soluteCoulFunction = '(1.0-(lambdaQ^2))*ONE_4PI_EPS0*charge/r;'
    soluteCoulFunction += 'ONE_4PI_EPS0 = %.16e;' % (ONE_4PI_EPS0)
    soluteCoulFunction += 'charge = charge1*charge2'
    SoluteCoulForce = mm.CustomNonbondedForce(soluteCoulFunction)
    #Note this lambdaQ will be different than for soft core (it's also named differently, which is CRITICAL)
    #This lambdaQ corresponds to the lambda that scales the charges to zero
    #To turn on this custom force at the same rate, need to multiply by (1.0-lambdaQ**2), which we do
    SoluteCoulForce.addGlobalParameter('lambdaQ', 1.0)
    SoluteCoulForce.addPerParticleParameter('charge')

    #Also create custom force for intramolecular alchemical LJ interactions
    #Could include with electrostatics, but nice to break up
    #We could also do this with a separate NonbondedForce object, but it would be a little more work, actually
    soluteLJFunction = '4.0*epsilon*x*(x-1.0); x = (sigma/r)^6;'
    soluteLJFunction += 'sigma=0.5*(sigma1+sigma2); epsilon=sqrt(epsilon1*epsilon2)'
    SoluteLJForce = mm.CustomNonbondedForce(soluteLJFunction)
    SoluteLJForce.addPerParticleParameter('sigma')
    SoluteLJForce.addPerParticleParameter('epsilon')

    #Loop over all particles and add to custom forces
    #As we go, will also collect full charges on the solute particles
    #AND we will set up the solute-solute interaction forces
    alchemicalCharges = [[0]] * len(allSoluteIndices)
    for ind in range(systemRef.getNumParticles()):
        #Get current parameters in non-bonded force
        [charge, sigma, epsilon] = NBForce.getParticleParameters(ind)
        #Make sure that sigma is not set to zero! Fine for some ways of writing LJ energy, but NOT OK for soft-core!
        if sigma / u.nanometer == 0.0:
            newsigma = 0.3 * u.nanometer  #This 0.3 is what's used by GROMACS as a default value for sc-sigma
        else:
            newsigma = sigma
        #Add the particle to the soft-core force (do for ALL particles)
        SoftCoreForce.addParticle([newsigma, epsilon])
        #Also add the particle to the solute only forces
        SoluteCoulForce.addParticle([charge])
        SoluteLJForce.addParticle([sigma, epsilon])
        #If the particle is in the alchemical molecule, need to set it's LJ interactions to zero in original force
        if ind in allSoluteIndices:
            NBForce.setParticleParameters(ind, charge, sigma, epsilon * 0.0)
            #And keep track of full charge so we can scale it right by lambda
            alchemicalCharges[allSoluteIndices.index(ind)] = charge

    #Now we need to handle exceptions carefully
    for ind in range(NBForce.getNumExceptions()):
        [p1, p2, excCharge, excSig,
         excEps] = NBForce.getExceptionParameters(ind)
        #For consistency, must add exclusions where we have exceptions for custom forces
        SoftCoreForce.addExclusion(p1, p2)
        SoluteCoulForce.addExclusion(p1, p2)
        SoluteLJForce.addExclusion(p1, p2)

    #Only compute interactions between the alchemical and other particles for the soft-core force
    SoftCoreForce.addInteractionGroup(alchemicalParticles, chemicalParticles)

    #And only compute alchemical/alchemical interactions for other custom forces
    SoluteCoulForce.addInteractionGroup(alchemicalParticles,
                                        alchemicalParticles)
    SoluteLJForce.addInteractionGroup(alchemicalParticles, alchemicalParticles)

    #Set other soft-core parameters as needed
    SoftCoreForce.setCutoffDistance(12.0 * u.angstroms)
    SoftCoreForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoftCoreForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoftCoreForce)

    #Set other parameters as needed - note that for the solute force would like to set no cutoff
    #However, OpenMM won't allow a bunch of potentials with cutoffs then one without...
    #So as long as the solute is smaller than the cut-off, won't have any problems!
    SoluteCoulForce.setCutoffDistance(12.0 * u.angstroms)
    SoluteCoulForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoluteCoulForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoluteCoulForce)

    SoluteLJForce.setCutoffDistance(12.0 * u.angstroms)
    SoluteLJForce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
    SoluteLJForce.setUseLongRangeCorrection(False)
    systemRef.addForce(SoluteLJForce)

    forceLabelsRef = getForceLabels(systemRef)

    decompEnergy(systemRef, struc.positions, labels=forceLabelsRef)

    #Run set of simulations for particle restrained in x and y to each quadrant of the surface
    #For best results, the configuration read in should have the solute right in the center of the surface
    #Will store the lambda biasing weights as we got to help speed convergence (hopefully)
    for xfrac in [0.25, 0.75]:
        for yfrac in [0.25, 0.75]:

            os.mkdir('Quad_%1.2fX_%1.2fY' % (xfrac, yfrac))
            os.chdir('Quad_%1.2fX_%1.2fY' % (xfrac, yfrac))

            restraintXYForce.setGlobalParameterDefaultValue(
                0, xfrac * top.box[0] * u.angstrom)
            restraintXYForce.setGlobalParameterDefaultValue(
                1, yfrac * top.box[1] * u.angstrom)

            decompEnergy(systemRef, struc.positions, labels=forceLabelsRef)

            #Do NVT simulation
            stateFileNVT, stateNVT = doSimNVT(top,
                                              systemRef,
                                              integratorRef,
                                              platform,
                                              prop,
                                              temperature,
                                              pos=struc.positions)

            #And do NPT simulation using state information from NVT
            stateFileNPT, stateNPT = doSimNPT(top,
                                              systemRef,
                                              integratorRef,
                                              platform,
                                              prop,
                                              temperature,
                                              state=stateFileNVT)

            #And do production run in expanded ensemble!
            stateFileProd, stateProd, weightsVec = doSimExpanded(
                top,
                systemRef,
                integratorRef,
                platform,
                prop,
                temperature,
                0,
                lambdaVec,
                allSoluteIndices,
                alchemicalCharges,
                state=stateFileNPT,
                nSteps=20000000,
                equilSteps=7500000)

            #Save final simulation configuration in .gro format (mainly for genetic algorithm)
            finalStruc = copy.deepcopy(struc)
            boxVecs = np.array(stateProd.getPeriodicBoxVectors().value_in_unit(
                u.angstrom))  # parmed works with angstroms
            finalStruc.box = np.array([
                boxVecs[0, 0], boxVecs[1, 1], boxVecs[2, 2], 90.0, 90.0, 90.0
            ])
            finalStruc.coordinates = np.array(
                stateProd.getPositions().value_in_unit(u.angstrom))
            finalStruc.save('prod.gro')

            os.chdir('../')
示例#30
0
def add_molecule_to_system(system,
                           molecule_system,
                           core_atoms,
                           variant,
                           atoms_to_exclude=[]):
    """
    Add the valence terms for the molecule from molecule_system.

    Parameters
    ----------
    system : simtk.openmm.System
       The system object to which the valence terms are to be added.
    molecule_system : simtk.openmm.System
       The system object from which core valence terms are to be taken.
    core_atoms : list of int
       The list of atom indices within molecule_system corresponding to core atoms.
    variant : int
       The variant index of this molecule if not a core fragment, or 0 if this is a core fragment and only core atoms are to be added.

    Returns
    -------
    mapping : dict of int
       mapping[index] is the atom index in `system` corresponding to atom `index` within `molecule_system`.

    """
    def _createCustomNonbondedForce(self,
                                    system,
                                    molecule_system,
                                    softcore_alpha=0.5,
                                    softcore_beta=12 * unit.angstrom**2):
        """
        Create alchemically-modified version of NonbondedForce.

        Parameters
        ----------
        system : simtk.openmm.System
            Alchemically-modified system being built.  This object will be modified.
        molecule_system : simtk.openmm.System
            Source molecule system to copy from.
        softcore_alpha : float, optional, default = 0.5
            Alchemical softcore parameter for Lennard-Jones.
        softcore_beta : simtk.unit.Quantity with units compatible with angstroms**2, optional, default = 12*angstrom**2
            Alchemical softcore parameter for electrostatics.

        TODO
        ----
        Try using a single, common "reff" effective softcore distance for both Lennard-Jones and Coulomb.

        """

        alchemical_atom_indices = self.ligand_atoms

        # Create a copy of the NonbondedForce to handle non-alchemical interactions.
        nonbonded_force = copy.deepcopy(reference_force)
        system.addForce(nonbonded_force)

        # Create CustomNonbondedForce objects to handle softcore interactions between alchemically-modified system and rest of system.

        # Create atom groups.
        natoms = system.getNumParticles()
        atomset1 = set(
            alchemical_atom_indices)  # only alchemically-modified atoms
        atomset2 = set(range(system.getNumParticles())
                       )  # all atoms, including alchemical region

        # CustomNonbondedForce energy expression.
        sterics_energy_expression = ""
        electrostatics_energy_expression = ""

        # Select functional form based on nonbonded method.
        method = reference_force.getNonbondedMethod()
        if method in [openmm.NonbondedForce.NoCutoff]:
            # soft-core Lennard-Jones
            sterics_energy_expression += "U_sterics = lambda_sterics*4*epsilon*x*(x-1.0); x = (sigma/reff_sterics)^6;"
            # soft-core Coulomb
            electrostatics_energy_expression += "U_electrostatics = ONE_4PI_EPS0*lambda_electrostatics*chargeprod/reff_electrostatics;"
        elif method in [
                openmm.NonbondedForce.CutoffPeriodic,
                openmm.NonbondedForce.CutoffNonPeriodic
        ]:
            # soft-core Lennard-Jones
            sterics_energy_expression += "U_sterics = lambda_sterics*4*epsilon*x*(x-1.0); x = (sigma/reff_sterics)^6;"
            # reaction-field electrostatics
            epsilon_solvent = reference_force.getReactionFieldDielectric()
            r_cutoff = reference_force.getCutoffDistance()
            electrostatics_energy_expression += "U_electrostatics = lambda_electrostatics*ONE_4PI_EPS0*chargeprod*(reff_electrostatics^(-1) + k_rf*reff_electrostatics^2 - c_rf);"
            k_rf = r_cutoff**(-3) * ((epsilon_solvent - 1) /
                                     (2 * epsilon_solvent + 1))
            c_rf = r_cutoff**(-1) * ((3 * epsilon_solvent) /
                                     (2 * epsilon_solvent + 1))
            electrostatics_energy_expression += "k_rf = %f;" % (
                k_rf / k_rf.in_unit_system(unit.md_unit_system).unit)
            electrostatics_energy_expression += "c_rf = %f;" % (
                c_rf / c_rf.in_unit_system(unit.md_unit_system).unit)
        elif method in [
                openmm.NonbondedForce.PME, openmm.NonbondedForce.Ewald
        ]:
            # soft-core Lennard-Jones
            sterics_energy_expression += "U_sterics = lambda_sterics*4*epsilon*x*(x-1.0); x = (sigma/reff_sterics)^6;"
            # Ewald direct-space electrostatics
            [alpha_ewald, nx, ny, nz] = reference_force.getPMEParameters()
            if alpha_ewald == 0.0:
                # If alpha is 0.0, alpha_ewald is computed by OpenMM from from the error tolerance.
                delta = reference_force.getEwaldErrorTolerance()
                r_cutoff = reference_force.getCutoffDistance()
                alpha_ewald = np.sqrt(-np.log(2 * delta)) / r_cutoff
            electrostatics_energy_expression += "U_electrostatics = lambda_electrostatics*ONE_4PI_EPS0*chargeprod*erfc(alpha_ewald*reff_electrostatics)/reff_electrostatics;"
            electrostatics_energy_expression += "alpha_ewald = %f;" % (
                alpha_ewald /
                alpha_ewald.in_unit_system(unit.md_unit_system).unit)
            # TODO: Handle reciprocal-space electrostatics
        else:
            raise Exception("Nonbonded method %s not supported yet." %
                            str(method))

        # Add additional definitions common to all methods.
        sterics_energy_expression += "reff_sterics = sigma*((softcore_alpha*(1.-lambda_sterics) + (r/sigma)^6))^(1/6);"  # effective softcore distance for sterics
        sterics_energy_expression += "softcore_alpha = %f;" % softcore_alpha
        electrostatics_energy_expression += "reff_electrostatics = sqrt(softcore_beta*(1.-lambda_electrostatics) + r^2);"  # effective softcore distance for electrostatics
        electrostatics_energy_expression += "softcore_beta = %f;" % (
            softcore_beta /
            softcore_beta.in_unit_system(unit.md_unit_system).unit)
        electrostatics_energy_expression += "ONE_4PI_EPS0 = %f;" % ONE_4PI_EPS0  # already in OpenMM units

        # Define mixing rules.
        sterics_mixing_rules = ""
        sterics_mixing_rules += "epsilon = sqrt(epsilon1*epsilon2);"  # mixing rule for epsilon
        sterics_mixing_rules += "sigma = 0.5*(sigma1 + sigma2);"  # mixing rule for sigma
        electrostatics_mixing_rules = ""
        electrostatics_mixing_rules += "chargeprod = charge1*charge2;"  # mixing rule for charges

        # Create CustomNonbondedForce to handle interactions between alchemically-modified atoms and rest of system.
        electrostatics_custom_nonbonded_force = openmm.CustomNonbondedForce(
            "U_electrostatics;" + electrostatics_energy_expression +
            electrostatics_mixing_rules)
        electrostatics_custom_nonbonded_force.addGlobalParameter(
            "lambda_electrostatics", 1.0)
        electrostatics_custom_nonbonded_force.addPerParticleParameter(
            "charge")  # partial charge
        sterics_custom_nonbonded_force = openmm.CustomNonbondedForce(
            "U_sterics;" + sterics_energy_expression + sterics_mixing_rules)
        sterics_custom_nonbonded_force.addGlobalParameter(
            "lambda_sterics", 1.0)
        sterics_custom_nonbonded_force.addPerParticleParameter(
            "sigma")  # Lennard-Jones sigma
        sterics_custom_nonbonded_force.addPerParticleParameter(
            "epsilon")  # Lennard-Jones epsilon

        # Set parameters to match reference force.
        sterics_custom_nonbonded_force.setUseSwitchingFunction(
            nonbonded_force.getUseSwitchingFunction())
        electrostatics_custom_nonbonded_force.setUseSwitchingFunction(False)
        sterics_custom_nonbonded_force.setCutoffDistance(
            nonbonded_force.getCutoffDistance())
        electrostatics_custom_nonbonded_force.setCutoffDistance(
            nonbonded_force.getCutoffDistance())
        sterics_custom_nonbonded_force.setSwitchingDistance(
            nonbonded_force.getSwitchingDistance())
        sterics_custom_nonbonded_force.setUseLongRangeCorrection(
            nonbonded_force.getUseDispersionCorrection())
        electrostatics_custom_nonbonded_force.setUseLongRangeCorrection(False)

        # Set periodicity and cutoff parameters corresponding to reference Force.
        if nonbonded_force.getNonbondedMethod() in [
                openmm.NonbondedForce.Ewald, openmm.NonbondedForce.PME,
                openmm.NonbondedForce.CutoffPeriodic
        ]:
            sterics_custom_nonbonded_force.setNonbondedMethod(
                openmm.CustomNonbondedForce.CutoffPeriodic)
            electrostatics_custom_nonbonded_force.setNonbondedMethod(
                openmm.CustomNonbondedForce.CutoffPeriodic)
        else:
            sterics_custom_nonbonded_force.setNonbondedMethod(
                nonbonded_force.getNonbondedMethod())
            electrostatics_custom_nonbonded_force.setNonbondedMethod(
                nonbonded_force.getNonbondedMethod())

        # Restrict interaction evaluation to be between alchemical atoms and rest of environment.
        # TODO: Exclude intra-alchemical region if we are separately handling that through a separate CustomNonbondedForce for decoupling.
        sterics_custom_nonbonded_force.addInteractionGroup(atomset1, atomset2)
        electrostatics_custom_nonbonded_force.addInteractionGroup(
            atomset1, atomset2)

        # Add custom forces.
        system.addForce(sterics_custom_nonbonded_force)
        system.addForce(electrostatics_custom_nonbonded_force)

        # Create CustomBondForce to handle exceptions for both kinds of interactions.
        custom_bond_force = openmm.CustomBondForce(
            "U_sterics + U_electrostatics;" + sterics_energy_expression +
            electrostatics_energy_expression)
        custom_bond_force.addGlobalParameter("lambda_electrostatics", 1.0)
        custom_bond_force.addGlobalParameter("lambda_sterics", 1.0)
        custom_bond_force.addPerBondParameter("chargeprod")  # charge product
        custom_bond_force.addPerBondParameter(
            "sigma")  # Lennard-Jones effective sigma
        custom_bond_force.addPerBondParameter(
            "epsilon")  # Lennard-Jones effective epsilon
        system.addForce(custom_bond_force)

        # Move NonbondedForce particle terms for alchemically-modified particles to CustomNonbondedForce.
        for particle_index in range(nonbonded_force.getNumParticles()):
            # Retrieve parameters.
            [charge, sigma,
             epsilon] = nonbonded_force.getParticleParameters(particle_index)
            # Add parameters to custom force handling interactions between alchemically-modified atoms and rest of system.
            sterics_custom_nonbonded_force.addParticle([sigma, epsilon])
            electrostatics_custom_nonbonded_force.addParticle([charge])
            # Turn off Lennard-Jones contribution from alchemically-modified particles.
            if particle_index in alchemical_atom_indices:
                nonbonded_force.setParticleParameters(particle_index,
                                                      0 * charge, sigma,
                                                      0 * epsilon)

        # Move NonbondedForce exception terms for alchemically-modified particles to CustomNonbondedForce/CustomBondForce.
        for exception_index in range(nonbonded_force.getNumExceptions()):
            # Retrieve parameters.
            [iatom, jatom, chargeprod, sigma,
             epsilon] = nonbonded_force.getExceptionParameters(exception_index)
            # Exclude this atom pair in CustomNonbondedForce.
            sterics_custom_nonbonded_force.addExclusion(iatom, jatom)
            electrostatics_custom_nonbonded_force.addExclusion(iatom, jatom)
            # Move exceptions involving alchemically-modified atoms to CustomBondForce.
            if self.annihilate_sterics and (iatom in alchemical_atom_indices
                                            ) and (jatom
                                                   in alchemical_atom_indices):
                # Add special CustomBondForce term to handle alchemically-modified Lennard-Jones exception.
                custom_bond_force.addBond(iatom, jatom,
                                          [chargeprod, sigma, epsilon])
                # Zero terms in NonbondedForce.
                nonbonded_force.setExceptionParameters(exception_index, iatom,
                                                       jatom, 0 * chargeprod,
                                                       sigma, 0 * epsilon)

        # TODO: Add back NonbondedForce terms for alchemical system needed in case of decoupling electrostatics or sterics via second CustomBondForce.
        # TODO: Also need to change current CustomBondForce to not alchemically disappear system.

        return

    # Build dict of forces.
    def create_force_dict(system):
        return {
            system.getForce(index).__class__.__name__: system.getForce(index)
            for index in range(system.getNumForces())
        }

    molecule_forces = create_force_dict(molecule_system)
    forces = create_force_dict(system)

    # Create Custom*Force classes if necessary.
    if 'CustomBondForce' not in forces:
        energy_expression = 'lambda*(K/2)*(r-length)^2;'
        energy_expression += 'lambda = (1-alchemical_lambda)*delta(variant) + alchemical_lambda*delta(variant-alchemical_variant);'
        custom_force = mm.CustomBondForce(energy_expression)
        custom_force.addGlobalParameter('alchemical_lambda', 0.0)
        custom_force.addGlobalParameter('alchemical_variant', 0.0)
        custom_force.addPerBondParameter('variant')
        custom_force.addPerBondParameter('length')
        custom_force.addPerBondParameter('K')
        system.addForce(custom_force)
        forces['CustomBondForce'] = custom_force
    if 'CustomAngleForce' not in forces:
        energy_expression = 'lambda*(K/2)*(theta-theta0)^2;'
        energy_expression += 'lambda = (1-alchemical_lambda)*delta(variant) + alchemical_lambda*delta(variant-alchemical_variant);'
        custom_force = mm.CustomAngleForce(energy_expression)
        custom_force.addGlobalParameter('alchemical_lambda', 0.0)
        custom_force.addGlobalParameter('alchemical_variant', 0.0)
        custom_force.addPerAngleParameter('variant')
        custom_force.addPerAngleParameter('theta0')
        custom_force.addPerAngleParameter('K')
        system.addForce(custom_force)
        forces['CustomAngleForce'] = custom_force
    if 'CustomTorsionForce' not in forces:
        energy_expression = 'lambda*K*(1+cos(periodicity*theta-phase));'
        energy_expression += 'lambda = (1-alchemical_lambda)*delta(variant) + alchemical_lambda*delta(variant-alchemical_variant);'
        custom_force = mm.CustomTorsionForce(energy_expression)
        custom_force.addGlobalParameter('alchemical_lambda', 0.0)
        custom_force.addGlobalParameter('alchemical_variant', 0.0)
        custom_force.addPerTorsionParameter('variant')
        custom_force.addPerTorsionParameter('periodicity')
        custom_force.addPerTorsionParameter('phase')
        custom_force.addPerTorsionParameter('K')
        system.addForce(custom_force)
        forces['CustomTorsionForce'] = custom_force
    if 'CustomNonbondedForce' not in forces:
        # DEBUG
        # TODO: Create proper CustomNonbondedForce here.
        energy_expression = "0.0;"
        custom_force = mm.CustomNonbondedForce(energy_expression)
        custom_force.addGlobalParameter('alchemical_lambda', 0.0)
        custom_force.addGlobalParameter('alchemical_variant', 0.0)
        custom_force.addPerParticleParameter('variant')
        custom_force.addPerParticleParameter('charge')
        custom_force.addPerParticleParameter('sigma')
        custom_force.addPerParticleParameter('epsilon')
        system.addForce(custom_force)
        forces['CustomNonbondedForce'] = custom_force

        # Add parameters for existing particles.
        for index in range(system.getNumParticles()):
            [charge, sigma,
             epsilon] = forces['NonbondedForce'].getParticleParameters(index)
            custom_force.addParticle([0, charge, sigma, epsilon])

    # Add particles to system.
    mapping = dict()  # mapping[index_in_molecule] = index_in_system
    for index_in_molecule in range(molecule_system.getNumParticles()):
        # Add all atoms, unless we're adding the core, in which case we just add core atoms.
        if (variant) or (index_in_molecule in core_atoms):
            # TODO: We may want to make masses lighter.
            index_in_system = system.addParticle(
                system.getParticleMass(index_in_molecule))
            mapping[index_in_molecule] = index_in_system

    # Constraints are not supported.
    # TODO: Later, consider supporting some constraints, such as those within core and those within variants.
    if (molecule_system.getNumConstraints() > 0):
        raise Exception(
            "Constraints are not supported for alchemically modified molecule."
        )

    # Process forces.
    # Valence terms involving only core atoms are created as Custom*Force classes where lambda=0 activates the "core" image and lambda=1 activates the "variant" image.
    for (force_name, force) in molecule_forces.iteritems():
        print force_name
        if force_name == 'HarmonicBondForce':
            for index in range(force.getNumBonds()):
                [atom_i, atom_j, length, K] = force.getBondParameters(index)
                if set([atom_i, atom_j]).issubset(core_atoms):
                    forces['CustomBondForce'].addBond(mapping[atom_i],
                                                      mapping[atom_j],
                                                      [variant, length, K])
                elif (variant):
                    forces[force_name].addBond(mapping[atom_i],
                                               mapping[atom_j], length, K)

        elif force_name == 'HarmonicAngleForce':
            for index in range(force.getNumAngles()):
                [atom_i, atom_j, atom_k, theta0,
                 K] = force.getAngleParameters(index)
                if set([atom_i, atom_j, atom_k]).issubset(core_atoms):
                    forces['CustomAngleForce'].addAngle(
                        mapping[atom_i], mapping[atom_j], mapping[atom_k],
                        [variant, theta0, K])
                elif (variant):
                    forces[force_name].addAngle(mapping[atom_i],
                                                mapping[atom_j],
                                                mapping[atom_k], theta0, K)

        elif force_name == 'PeriodicTorsionForce':
            for index in range(force.getNumTorsions()):
                [atom_i, atom_j, atom_k, atom_l, periodicity, phase,
                 K] = force.getTorsionParameters(index)
                if set([atom_i, atom_j, atom_k, atom_l]).issubset(core_atoms):
                    forces['CustomTorsionForce'].addTorsion(
                        mapping[atom_i], mapping[atom_j], mapping[atom_k],
                        mapping[atom_l], [variant, periodicity, phase, K])
                elif (variant):
                    forces[force_name].addTorsion(mapping[atom_i],
                                                  mapping[atom_j],
                                                  mapping[atom_k],
                                                  mapping[atom_l], periodicity,
                                                  phase, K)

        elif force_name == 'NonbondedForce':
            for index in range(force.getNumParticles()):
                # TODO: Nonbonded terms will have to be handled as CustomNonbondedForce terms.
                [charge, sigma, epsilon] = force.getParticleParameters(index)
                if set([index]).issubset(core_atoms):
                    forces[force_name].addParticle(0.0 * charge, sigma,
                                                   0.0 * epsilon)
                    forces['CustomNonbondedForce'].addParticle(
                        [variant, charge, sigma, epsilon])
                elif (variant):
                    forces[force_name].addParticle(0.0 * charge, sigma,
                                                   0.0 * epsilon)
                    forces['CustomNonbondedForce'].addParticle(
                        [variant, charge, sigma, epsilon])

            for index in range(force.getNumExceptions()):
                [atom_i, atom_j, chargeProd, sigma,
                 epsilon] = force.getExceptionParameters(index)
                if set([atom_i, atom_j]).issubset(core_atoms):
                    # TODO: Nonbonded exceptions will have to be handled as CustomBondForce terms.
                    forces[force_name].addException(
                        mapping[atom_i], mapping[atom_j],
                        0.0 * unit.elementary_charge**2, 1.0 * unit.angstrom,
                        0.0 * unit.kilocalories_per_mole)
                elif (variant):
                    forces[force_name].addException(mapping[atom_i],
                                                    mapping[atom_j],
                                                    chargeProd, sigma, epsilon)

        # TODO: Add GB force processing.

    # Add exclusions to previous variants and core.
    for atom_i in mapping.values():
        for atom_j in atoms_to_exclude:
            forces['NonbondedForce'].addException(
                atom_i, atom_j, 0.0 * unit.elementary_charge**2,
                1.0 * unit.angstrom, 0.0 * unit.kilocalories_per_mole)
            forces['CustomNonbondedForce'].addExclusion(atom_i, atom_j)

    print system.getNumParticles(), forces['NonbondedForce'].getNumParticles()

    return mapping