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}"
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
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)
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
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)
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
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)
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])
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)
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])
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
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)
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
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)
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)
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
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)
# 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
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)
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
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])
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)
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
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))
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('../')
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