def add_cnb(system, indices, suffix='', lambda0=1.0): """ takes in a system, turns off alchemical only adds a single lambda controlling parameter! suffix is for adding a modifier to change the name of the lambda parameter """ # Retrieve the NonbondedForce forces = {force.__class__.__name__: force for force in system.getForces()} nbforce = forces['NonbondedForce'] # Add a CustomNonbondedForce to handle only alchemically-modified interactions alchemical_particles = set(list(indices)) chemical_particles = set(range( system.getNumParticles())) - alchemical_particles energy_function = 'lambda{}*4*epsilon*x*(x-1.0); x = (sigma/reff_sterics)^6;'.format( suffix) energy_function += 'reff_sterics = sigma*(0.5*(1.0-lambda{}) + (r/sigma)^6)^(1/6);'.format( suffix) energy_function += 'sigma = 0.5*(sigma1+sigma2); epsilon = sqrt(epsilon1*epsilon2);' custom_force = openmm.CustomNonbondedForce(energy_function) custom_force.addGlobalParameter('lambda{}'.format(suffix), lambda0) custom_force.addPerParticleParameter('sigma') custom_force.addPerParticleParameter('epsilon') for index in range(system.getNumParticles()): [charge, sigma, epsilon] = nbforce.getParticleParameters(index) custom_force.addParticle([sigma, epsilon]) if index in alchemical_particles: nbforce.setParticleParameters(index, charge * 0, sigma, epsilon * 0) custom_force.addInteractionGroup(alchemical_particles, chemical_particles) system.addForce(custom_force)
def remove_qmmm_elec_force(self, system, qm_index, mm_index, qm_cutoff): forces = { force.__class__.__name__: force for force in system.getForces() } reference_force = forces['NonbondedForce'] ONE_4PI_EPS0 = 138.935456 if qm_cutoff is None: qm_cutoff = reference_force.getCutoffDistance() expression = '-1*ONE_4PI_EPS0*chargeprod/r;' expression += 'chargeprod = charge1*charge2;' expression += 'ONE_4PI_EPS0 = %.16e;' % (ONE_4PI_EPS0) force = openmm.CustomNonbondedForce(expression) force.addPerParticleParameter("charge") force.setUseSwitchingFunction(False) force.setCutoffDistance(qm_cutoff) force.setUseLongRangeCorrection(False) for index in range(reference_force.getNumParticles()): [charge, sigma, epsilon] = reference_force.getParticleParameters(index) force.addParticle([charge]) for index in range(reference_force.getNumExceptions()): [iatom, jatom, chargeprod, sigma, epsilon] = reference_force.getExceptionParameters(index) force.addExclusion(iatom, jatom) force.setForceGroup(reference_force.getForceGroup()) force.addInteractionGroup(qm_index, mm_index) system.addForce(force) return force
def soft_lennard_jones_force(sim_object, epsilon=0.42, trunc=2, cutoff=2.5): """A softened version of lennard-Jones force. Now we're moving to polynomial forces, so go there instead. """ nbCutOffDist = sim_object.conlen * cutoff repul_energy = ( "step(REPcut2 - REPU) * REPU +" " step(REPU - REPcut2) * REPcut2 * (1 + tanh(REPU/REPcut2 - 1));" "REPU = 4 * REPe * ((REPsigma/r2)^12 - (REPsigma/r2)^6);" "r2 = (r^10. + (REPsigma03)^10.)^0.1") sim_object.force_dict["Nonbonded"] = openmm.CustomNonbondedForce( repul_energy) repulforceGr = sim_object.force_dict["Nonbonded"] repulforceGr.addGlobalParameter("REPe", sim_object.kT * epsilon) repulforceGr.addGlobalParameter("REPsigma", sim_object.conlen) repulforceGr.addGlobalParameter("REPsigma03", 0.3 * sim_object.conlen) repulforceGr.addGlobalParameter("REPcut", sim_object.kT * trunc) repulforceGr.addGlobalParameter("REPcut2", 0.5 * trunc * sim_object.kT) for _ in range(sim_object.N): repulforceGr.addParticle(()) repulforceGr.setCutoffDistance(nbCutOffDist)
def AddAlchemyForces(system, ligand_ind): """ Input an OpenMM 'system' object and the indices of the ligand No output. Function adds alchemical nonbonded forces to the system """ forces = {force.__class__.__name__: force for force in system.getForces()} nbforce = forces['NonbondedForce'] ligand = ligand_ind protein = set(range(system.getNumParticles())) - ligand alchemical_energy = 'lambda*4*epsilon*x*(x-1.0); x = (sigma/reff_sterics)^6;' alchemical_energy += 'reff_sterics = sigma*(0.5*(1.0-lambda) + (r/sigma)^6)^(1/6);' alchemical_energy += 'sigma = 0.5*(sigma1+sigma2); epsilon = sqrt(epsilon1*epsilon2);' alchemical_force = mm.CustomNonbondedForce(alchemical_energy) alchemical_force.addGlobalParameter('lambda', 1.0) alchemical_force.addPerParticleParameter('sigma') alchemical_force.addPerParticleParameter('epsilon') for atm in range(system.getNumParticles()): # Get the atom's default nonbonded parameters [charge, sigma, epsilon] = nbforce.getParticleParameters(atm) alchemical_force.addParticle([sigma, epsilon]) if atm in ligand: # TODO: Not actually sure which '*0' options are necessary nbforce.setParticleParameters(atm, charge * 0, sigma, epsilon) alchemical_force.addInteractionGroup(ligand, protein) system.addForce(alchemical_force)
def opls_lj(self): """ This function changes the standard OpenMM combination rules to use OPLS, execp and normal pairs are only required if their are virtual sites in the molecule. """ # Get the system information from the openmm system forces = {self.system.getForce(index).__class__.__name__: self.system.getForce(index) for index in range(self.system.getNumForces())} # Use the nondonded_force to get the same rules nonbonded_force = forces['NonbondedForce'] lorentz = mm.CustomNonbondedForce( 'epsilon*((sigma/r)^12-(sigma/r)^6); sigma=sqrt(sigma1*sigma2); epsilon=sqrt(epsilon1*epsilon2)*4.0') lorentz.setNonbondedMethod(nonbonded_force.getNonbondedMethod()) lorentz.addPerParticleParameter('sigma') lorentz.addPerParticleParameter('epsilon') lorentz.setCutoffDistance(nonbonded_force.getCutoffDistance()) self.system.addForce(lorentz) l_j_set = {} # For each particle, calculate the combination list again for index in range(nonbonded_force.getNumParticles()): charge, sigma, epsilon = nonbonded_force.getParticleParameters(index) l_j_set[index] = (sigma, epsilon, charge) lorentz.addParticle([sigma, epsilon]) nonbonded_force.setParticleParameters(index, charge, 0, 0) for i in range(nonbonded_force.getNumExceptions()): (p1, p2, q, sig, eps) = nonbonded_force.getExceptionParameters(i) # ALL THE 12,13 and 14 interactions are EXCLUDED FROM CUSTOM NONBONDED FORCE lorentz.addExclusion(p1, p2) if eps._value != 0.0: charge = 0.5 * (l_j_set[p1][2] * l_j_set[p2][2]) sig14 = np.sqrt(l_j_set[p1][0] * l_j_set[p2][0]) nonbonded_force.setExceptionParameters(i, p1, p2, charge, sig14, eps)
def opls_lj(self): # Get the system information from the OpenMM system. forces = { self.system.getForce(index).__class__.__name__: self.system.getForce(index) for index in range(self.system.getNumForces()) } # Use the nonbonded_force to get the same rules. nonbonded_force = forces["NonbondedForce"] lorentz = openmm.CustomNonbondedForce( "epsilon*((sigma/r)^12-(sigma/r)^6); sigma=sqrt(sigma1*sigma2); epsilon=sqrt(epsilon1*epsilon2)*4.0" ) lorentz.setNonbondedMethod(nonbonded_force.getNonbondedMethod()) lorentz.setCutoffDistance(nonbonded_force.getCutoffDistance()) lorentz.addPerParticleParameter("sigma") lorentz.addPerParticleParameter("epsilon") self.system.addForce(lorentz) l_j_set = {} # For each particle, calculate the combination list again. for index in range(nonbonded_force.getNumParticles()): charge, sigma, epsilon = nonbonded_force.getParticleParameters( index) l_j_set[index] = AtomParams(charge, sigma, epsilon) lorentz.addParticle([sigma, epsilon]) nonbonded_force.setParticleParameters(index, charge, 0, 0) exclusions = {} for i in range(nonbonded_force.getNumExceptions()): p1, p2, q, _, eps = nonbonded_force.getExceptionParameters(i) # store the index of the exception by the sorted atom keys # ALL THE 12, 13 and 14 interactions are EXCLUDED FROM CUSTOM NONBONDED FORCE lorentz.addExclusion(p1, p2) exclusions[tuple(sorted((p1, p2)))] = i if eps._value != 0.0: sig14 = np.sqrt(l_j_set[p1].sigma * l_j_set[p2].sigma) nonbonded_force.setExceptionParameters(i, p1, p2, q, sig14, eps) # If there is a virtual site in the molecule we have to change the exceptions and pairs lists if self.molecule.extra_sites is not None: # get the interaction lists excep_pairs, normal_pairs = self.get_vsite_interactions() for pair in excep_pairs: # scale 14 interactions atom1 = l_j_set[pair[0]] atom2 = l_j_set[pair[1]] q = atom1.charge * atom2.charge * 0.5 if pair not in exclusions: lorentz.addExclusion(*pair) nonbonded_force.addException(*pair, q, 0, 0, True) for pair in normal_pairs: # add the normal pairs here atom1 = l_j_set[pair[0]] atom2 = l_j_set[pair[1]] q = atom1.charge * atom2.charge if pair not in exclusions: lorentz.addExclusion(*pair) nonbonded_force.addException(*pair, q, 0, 0, True)
def opls_lj(system): forces = { system.getForce(indx).__class__.__name__: system.getForce(indx) for indx in range(system.getNumForces()) } nonbonded_force = forces['NonbondedForce'] lorentz = mm.CustomNonbondedForce( 'epsilon*((sigma/r)^12-(sigma/r)^6); sigma=sqrt(sigma1*sigma2); epsilon=sqrt(epsilon1*epsilon2)*4.0' ) lorentz.setNonbondedMethod(nonbonded_force.getNonbondedMethod()) lorentz.addPerParticleParameter('sigma') lorentz.addPerParticleParameter('epsilon') lorentz.setCutoffDistance(nonbonded_force.getCutoffDistance()) system.addForce(lorentz) l_j_set = {} for index in range(nonbonded_force.getNumParticles()): charge, sigma, epsilon = nonbonded_force.getParticleParameters(index) l_j_set[index] = (sigma, epsilon) lorentz.addParticle([sigma, epsilon]) nonbonded_force.setParticleParameters(index, charge, sigma, epsilon * 0) for i in range(nonbonded_force.getNumExceptions()): (p1, p2, q, sig, eps) = nonbonded_force.getExceptionParameters(i) lorentz.addExclusion(p1, p2) if eps._value != 0.0: sig14 = (l_j_set[p1][0] * l_j_set[p2][0])**0.5 eps14 = (l_j_set[p1][1] * l_j_set[p2][1])**0.5 nonbonded_force.setExceptionParameters(i, p1, p2, q, sig14, eps14) return system
def grosberg_repulsive_force( sim_object, trunc=None, radiusMult=1.0, name="grosberg_repulsive",trunc_function = "min(trunc1, trunc2)", ): """This is the fastest non-transparent repulsive force. (that preserves topology, doesn't allow chain passing) Done according to the paper: (Halverson, Jonathan D., et al. "Molecular dynamics simulation study of nonconcatenated ring polymers in a melt. I. Statics." The Journal of chemical physics 134 (2011): 204904.) Parameters ---------- trunc : None, float or N-array of floats "transparency" values for each particular particle, which correspond to the truncation values in kT for the grosberg repulsion energy between a pair of such particles. Value of 1.5 yields frequent passing, 3 - average passing, 5 - rare passing. radiusMult : float (optional) Multiplier for the size of the force. To make scale the energy larger, set to be more than 1. trunc_function : str (optional) a formula to calculate the truncation between a pair of particles with transparencies trunc1 and trunc2 Default is min(trunc1, trunc2) """ radius = sim_object.conlen * radiusMult nbCutOffDist = radius * 2.0 ** (1.0 / 6.0) if trunc is None: repul_energy = "4 * e * ((sigma/r)^12 - (sigma/r)^6) + e" else: trunc = _to_array_1d(trunc, sim_object.N) repul_energy = ( "step(cut2*trunc_pair - U) * U" " + step(U - cut2*trunc_pair) * cut2 * trunc_pair * (1 + tanh(U/(cut2*trunc_pair) - 1));" f"trunc_pair={trunc_function};" "U = 4 * e * ((sigma/r2)^12 - (sigma/r2)^6) + e;" "r2 = (r^10. + (sigma03)^10.)^0.1" ) force = openmm.CustomNonbondedForce(repul_energy) force.name = name force.addGlobalParameter("e", sim_object.kT) force.addGlobalParameter("sigma", radius) force.addGlobalParameter("sigma03", 0.3 * radius) if trunc is not None: force.addGlobalParameter("cut2", 0.5 * sim_object.kT) force.addPerParticleParameter("trunc") for i in range(sim_object.N): # adding all the particles on which force acts force.addParticle([float(trunc[i])]) else: for i in range(sim_object.N): # adding all the particles on which force acts force.addParticle(()) force.setCutoffDistance(nbCutOffDist) return force
def CLPolCoulTT(system, donors, b=45.0): ''' Apply Tang-Toennies damping for the Coulomb interactions between selected H-bond donors and Drude dipoles. Parameters ---------- system : mm.System donors : list of int Indexes of particles served as H-bond donors b : float, optional b in unit of /nm Returns ------- force : mm.CustomNonbondedForce ''' nbforce: mm.NonbondedForce = next(f for f in system.getForces() if type(f) == mm.NonbondedForce) dforce: mm.DrudeForce = next(f for f in system.getForces() if type(f) == mm.DrudeForce) drude_pairs = {} # {parent: drude} dipole_set = set() for i in range(dforce.getNumParticles()): drude, parent, p2, p3, p4, q, alpha, aniso12, aniso34 = dforce.getParticleParameters( i) drude_pairs[parent] = drude dipole_set.add(parent) dipole_set.add(drude) ttforce = mm.CustomNonbondedForce('-%.6f*q1*q2/r*beta*gamma;' 'beta=exp(-br);' 'gamma=1+br+br*br/2+br2*br/6+br2*br2/24;' 'br2=br*br;' 'br=%.6f*r' % (CONST.ONE_4PI_EPS0, b)) ttforce.addPerParticleParameter('q') for i in range(system.getNumParticles()): if i in drude_pairs: q, _, _ = nbforce.getParticleParameters(drude_pairs[i]) q = -q else: q, _, _ = nbforce.getParticleParameters(i) q = q.value_in_unit(qe) ttforce.addParticle([q]) ttforce.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) ttforce.setCutoffDistance(1.2 * nm) ttforce.addInteractionGroup(set(donors), dipole_set) ttforce.setForceGroup(9) # map all the exclusions from NonbondedForce for i in range(nbforce.getNumExceptions()): ii, jj, _, _, _ = nbforce.getExceptionParameters(i) ttforce.addExclusion(ii, jj) system.addForce(ttforce) return ttforce
def create_c6c12(cutoff: Quantity) -> mm.CustomNonbondedForce: potential = mm.CustomNonbondedForce('C12_ij/(r6^2) - C6_ij/r6; ' 'r6 = r^6; ' 'C6_ij = sqrt(C61*C62); ' 'C12_ij = sqrt(C121*C122)') potential.addPerParticleParameter('C6') potential.addPerParticleParameter('C12') potential.setCutoffDistance(cutoff) potential.setNonbondedMethod(potential.CutoffPeriodic) return potential
def create_epsilon_sigma(cutoff: Quantity) -> mm.CustomNonbondedForce: potential = mm.CustomNonbondedForce( '4 * epsilon_ij * (frac6^2 - frac6); ' 'frac6 = (sigma_ij/r)^6; ' 'epsilon_ij = sqrt(epsilon1*epsilon2); ' 'sigma_ij = 0.5 * (sigma1 + sigma2)') potential.addPerParticleParameter('sigma') potential.addPerParticleParameter('epsilon') potential.setCutoffDistance(cutoff) potential.setNonbondedMethod(potential.CutoffPeriodic) return potential
def test_setting(self, geometric_ethane_system_topology): nb_force = openmm.CustomNonbondedForce('epsilon1*epsilon2*(sigr6^2-sigr6); sigr6=sigr2*sigr2*sigr2; ' 'sigr2=(sigc/r)^2; sigc=sigma1*sigma2') nb_force.addPerParticleParameter('epsilon') nb_force.addPerParticleParameter('sigma') nb_force.addParticle((1*unit.kilojoule_per_mole, 2*unit.nanometer)) my_ommp = Ommperator(geometric_ethane_system_topology[0], geometric_ethane_system_topology[1]) my_nb_ommp = CustomNonbondedForceOmmperator(my_ommp, nb_force, 0) my_nb_ommp.set_params(10*unit.kilojoule_per_mole, -1) assert my_nb_ommp.parameters == nb_force.getParticleParameters(0) assert my_nb_ommp.parameters[0] == 10 assert my_nb_ommp.parameters[1] == 2 my_nb_ommp.set_params(-1, 20*unit.nanometer) assert my_nb_ommp.parameters == nb_force.getParticleParameters(0) assert my_nb_ommp.parameters[0] == 10 assert my_nb_ommp.parameters[1] == 20 my_nb_ommp.set_params(epsilon=100*unit.kilojoule_per_mole) assert my_nb_ommp.parameters == nb_force.getParticleParameters(0) assert my_nb_ommp.parameters[0] == 100 assert my_nb_ommp.parameters[1] == 20 my_nb_ommp.set_params(sigma=200*unit.nanometer) assert my_nb_ommp.parameters == nb_force.getParticleParameters(0) assert my_nb_ommp.parameters[0] == 100 assert my_nb_ommp.parameters[1] == 200 my_nb_ommp.set_params(1000*unit.kilojoule_per_mole, 2000*unit.nanometer) assert my_nb_ommp.parameters == nb_force.getParticleParameters(0) assert my_nb_ommp.parameters[0] == 1000 assert my_nb_ommp.parameters[1] == 2000 my_nb_ommp.set_params(epsilon=10000*unit.kilojoule_per_mole, sigma=20000*unit.nanometer) assert my_nb_ommp.parameters == nb_force.getParticleParameters(0) assert my_nb_ommp.parameters[0] == 10000 assert my_nb_ommp.parameters[1] == 20000 with pytest.raises(ValueError): my_nb_ommp.set_params(1,2,epsilon=3, sigma=4)
def test_tabulatedFunction(self): f = mm.CustomNonbondedForce('g(r)') r = np.linspace(0, 10) g_of_r = np.sin(r) indx = f.addFunction('g', g_of_r, np.min(r), np.max(r)) name, g_of_r_out, min_r_out, max_r_out = f.getFunctionParameters(indx) np.testing.assert_array_almost_equal(g_of_r, np.asarray(g_of_r_out)) assert min_r_out == np.min(r) assert max_r_out == np.max(r)
def add_excluded_volume(system: mm.System, args: ListOfArgs): print(" Adding excluded volume...") print(f" epsilon = {args.EV_EPSILON}") print(f" sigma = {args.EV_SIGMA}") ev_force = mm.CustomNonbondedForce('epsilon*((sigma1+sigma2)/r)^12') ev_force.addGlobalParameter('epsilon', defaultValue=args.EV_EPSILON) ev_force.addPerParticleParameter('sigma') system.addForce(ev_force) counter = 0 for i in range(system.getNumParticles()): ev_force.addParticle([args.EV_SIGMA]) counter += 1 print(f" {counter} ev interactions added.")
def __init__(self, system, atoms, group=0): self.this = copy.deepcopy(system).this nonbonded = self.getForce(atomsmm.findNonbondedForce(self)) potential = '4*lambda_vdw*epsilon*(1 - x)/x^2' potential += '; x=(r/sigma)^6 + 0.5*(1 - lambda_vdw)' potential += '; sigma = 0.5*(sigma1 + sigma2)' potential += '; epsilon = sqrt(epsilon1*epsilon2)' softcore = openmm.CustomNonbondedForce(potential) if nonbonded.getNonbondedMethod() == openmm.NonbondedForce.NoCutoff: softcore.setNonbondedMethod(openmm.CustomNonbondedForce.NoCutoff) else: softcore.setNonbondedMethod( openmm.CustomNonbondedForce.CutoffPeriodic) softcore.setCutoffDistance(nonbonded.getCutoffDistance()) softcore.setUseSwitchingFunction(nonbonded.getUseSwitchingFunction()) softcore.setSwitchingDistance(nonbonded.getSwitchingDistance()) # softcore.setUseLongRangeCorrection(nonbonded.getUseDispersionCorrection()) softcore.setUseLongRangeCorrection(False) softcore.addGlobalParameter('lambda_vdw', 1.0) softcore.addPerParticleParameter('sigma') softcore.addPerParticleParameter('epsilon') all = range(nonbonded.getNumParticles()) for index in all: _, sigma, epsilon = nonbonded.getParticleParameters(index) softcore.addParticle([sigma, epsilon]) softcore.addInteractionGroup(atoms, set(all) - set(atoms)) softcore.setForceGroup(group) softcore.addEnergyParameterDerivative('lambda_vdw') self.addForce(softcore) parameters = [] for index in atoms: parameters.append(nonbonded.getParticleParameters(index)) nonbonded.setParticleParameters(index, 0.0, 1.0, 0.0) exception_pairs = [] for index in range(nonbonded.getNumExceptions()): i, j, _, _, _ = nonbonded.getExceptionParameters(index) if set([i, j]).issubset(atoms): exception_pairs.append(set([i, j])) for i, j in itertools.combinations(atoms, 2): if set([i, j]) not in exception_pairs: q1, sig1, eps1 = parameters[i] q2, sig2, eps2 = parameters[j] nonbonded.addException(i, j, q1 * q2, (sig1 + sig2) / 2, np.sqrt(eps1 * eps2)) softcore.addExclusion( i, j) # Needed for matching exception number
def test_parsing(self, geometric_ethane_system_topology): nb_force = openmm.CustomNonbondedForce('epsilon1*epsilon2*(sigr6^2-sigr6); sigr6=sigr2*sigr2*sigr2; ' 'sigr2=(sigc/r)^2; sigc=sigma1*sigma2') nb_force.addPerParticleParameter('epsilon') nb_force.addPerParticleParameter('sigma') nb_force.addParticle((1*unit.kilojoule_per_mole, 2*unit.nanometer)) my_ommp = Ommperator(geometric_ethane_system_topology[0], geometric_ethane_system_topology[1]) my_nb_ommp = CustomNonbondedForceOmmperator(my_ommp, nb_force, 0) assert my_nb_ommp.parameters == nb_force.getParticleParameters(0) assert my_nb_ommp.energy_function == nb_force.getEnergyFunction() assert my_nb_ommp.parameter_index['epsilon'] == 0 assert my_nb_ommp.parameter_index['sigma'] == 1
def test_modify_omm(self, geometric_ethane_system_topology): nb_force = openmm.CustomNonbondedForce('epsilon1*epsilon2*(sigr6^2-sigr6); sigr6=sigr2*sigr2*sigr2; ' 'sigr2=(sigc/r)^2; sigc=sigma1*sigma2') nb_force.addPerParticleParameter('epsilon') nb_force.addPerParticleParameter('sigma') nb_force.addParticle((1*unit.kilojoule_per_mole, 2*unit.nanometer)) nb_force.setParticleParameters(0, (10*unit.kilojoule_per_mole, 20*unit.nanometer)) my_ommp = Ommperator(geometric_ethane_system_topology[0], geometric_ethane_system_topology[1]) my_nb_ommp = CustomNonbondedForceOmmperator(my_ommp, nb_force, 0) assert my_nb_ommp.parameters == nb_force.getParticleParameters(0) assert my_nb_ommp.parameters[0] == 10 assert my_nb_ommp.parameters[1] == 20
def polynomial_repulsive( sim_object, trunc=3.0, radiusMult=1.0, name="polynomial_repulsive" ): """This is a simple polynomial repulsive potential. It has the value of `trunc` at zero, stays flat until 0.6-0.7 and then drops to zero together with its first derivative at r=1.0. See the gist below with an example of the potential. https://gist.github.com/mimakaev/0327bf6ffe7057ee0e0625092ec8e318 Parameters ---------- trunc : float the energy value around r=0 """ radius = sim_object.conlen * radiusMult nbCutOffDist = radius repul_energy = ( "rsc12 * (rsc2 - 1.0) * REPe / emin12 + REPe;" "rsc12 = rsc4 * rsc4 * rsc4;" "rsc4 = rsc2 * rsc2;" "rsc2 = rsc * rsc;" "rsc = r / REPsigma * rmin12;" ) force = openmm.CustomNonbondedForce(repul_energy) force.name = name force.addGlobalParameter("REPe", trunc * sim_object.kT) force.addGlobalParameter("REPsigma", radius) # Coefficients for x^8*(x*x-1) # force.addGlobalParameter('emin12', 256.0 / 3125.0) # force.addGlobalParameter('rmin12', 2.0 / np.sqrt(5.0)) # Coefficients for x^12*(x*x-1) force.addGlobalParameter("emin12", 46656.0 / 823543.0) force.addGlobalParameter("rmin12", np.sqrt(6.0 / 7.0)) for _ in range(sim_object.N): force.addParticle(()) force.setCutoffDistance(nbCutOffDist) return force
def grosberg_repulsive_force(sim_object, trunc=None, radiusMult=1.0, name="grosberg_repulsive"): """This is the fastest non-transparent repulsive force. (that preserves topology, doesn't allow chain passing) Done according to the paper: (Halverson, Jonathan D., et al. "Molecular dynamics simulation study of nonconcatenated ring polymers in a melt. I. Statics." The Journal of chemical physics 134 (2011): 204904.) Parameters ---------- trunc : None or float truncation energy in kT, used for chain crossing. Value of 1.5 yields frequent passing, 3 - average passing, 5 - rare passing. """ radius = sim_object.conlen * radiusMult nbCutOffDist = radius * 2.0**(1.0 / 6.0) if trunc is None: repul_energy = "4 * e * ((sigma/r)^12 - (sigma/r)^6) + e" else: repul_energy = ("step(cut2 - U) * U" " + step(U - cut2) * cut2 * (1 + tanh(U/cut2 - 1));" "U = 4 * e * ((sigma/r2)^12 - (sigma/r2)^6) + e;" "r2 = (r^10. + (sigma03)^10.)^0.1") force = openmm.CustomNonbondedForce(repul_energy) force.name = name force.addGlobalParameter("e", sim_object.kT) force.addGlobalParameter("sigma", radius) force.addGlobalParameter("sigma03", 0.3 * radius) if trunc is not None: force.addGlobalParameter("cut", sim_object.kT * trunc) force.addGlobalParameter("cut2", 0.5 * trunc * sim_object.kT) for _ in range(sim_object.N): force.addParticle(()) force.setCutoffDistance(nbCutOffDist) return force
def opls(system): """Apply the opls combination rule to the system.""" from numpy import sqrt import simtk.openmm as mm # get system information from the openmm system forces = { system.getForce(index).__class__.__name__: system.getForce(index) for index in range(system.getNumForces()) } # use the nondonded_force tp get the same rules nonbonded_force = forces['NonbondedForce'] lorentz = mm.CustomNonbondedForce( 'epsilon*((sigma/r)^12-(sigma/r)^6); sigma=sqrt(sigma1*sigma2); epsilon=sqrt(epsilon1*epsilon2)*4.0' ) lorentz.setNonbondedMethod(mm.CustomNonbondedForce.NoCutoff) lorentz.addPerParticleParameter('sigma') lorentz.addPerParticleParameter('epsilon') lorentz.setCutoffDistance(nonbonded_force.getCutoffDistance()) system.addForce(lorentz) ljset = {} # Now for each particle calculate the combination list again for index in range(nonbonded_force.getNumParticles()): charge, sigma, epsilon = nonbonded_force.getParticleParameters( index) # print(nonbonded_force.getParticleParameters(index)) ljset[index] = (sigma, epsilon) lorentz.addParticle([sigma, epsilon]) nonbonded_force.setParticleParameters(index, charge, 0, 0) for i in range(nonbonded_force.getNumExceptions()): (p1, p2, q, sig, eps) = nonbonded_force.getExceptionParameters(i) # ALL THE 12,13 interactions are EXCLUDED FROM CUSTOM NONBONDED FORCE # All 1,4 are scaled by the amount in the xml file lorentz.addExclusion(p1, p2) if eps._value != 0.0: # combine sigma using the geometric combination rule sig14 = sqrt(ljset[p1][0] * ljset[p2][0]) nonbonded_force.setExceptionParameters(i, p1, p2, q, sig14, eps) return system
def minimizing_repulsive_Force(sim_object): """ Adds a special force which could be use for very efficient resolution of crossings Use this force to perform (local) energy minimization if your monomers are all "on top of each other" E.g. if you start your simulations with fractional brownyan motion with h < 0.4 Then switch to a normal force, and re-do energy minimization. """ radius = sim_object.conlen * 1.3 nbCutOffDist = radius * 1.0 repul_energy = "1000* REPe * (1-r/REPr)^2 " sim_object.force_dict[ "Nonbonded_minimizing_Force"] = openmm.CustomNonbondedForce( repul_energy) repulforceGr = sim_object.force_dict["Nonbonded_minimizing_Force"] repulforceGr.addGlobalParameter("REPe", sim_object.kT) repulforceGr.addGlobalParameter("REPr", sim_object.kT) for _ in range(sim_object.N): repulforceGr.addParticle(()) repulforceGr.setCutoffDistance(nbCutOffDist)
def __init__(self, alchemical_system, grid): self._system = openmm.System() for i in range(alchemical_system.getNumParticles()): self._system.addParticle(alchemical_system.getParticleMass(i)) self._system.setDefaultPeriodicBoxVectors( *alchemical_system.getDefaultPeriodicBoxVectors()) self._context = None self._numForces = len(grid) original = alchemical_system._alchemical_vdw_force # nonbonded = openmm.NonbondedForce() # for i in range(original.getNumParticles()): # nonbonded.addParticle(*original.getParticleParameters(i)) # nonbonded.setForceGroup(31) # self._system.addForce(nonbonded) # For keeping neighbor list for index, value in enumerate(grid): ljsoft = f'4*lambda*epsilon*x*(x - 1)' ljsoft += f'; x = 1/((r/sigma)^6 + 0.5*(1-lambda))' ljsoft += f'; lambda = {value}' ljsoft += f'; sigma = 0.5*(sigma1 + sigma2)' ljsoft += f'; epsilon = sqrt(epsilon1*epsilon2)' force = openmm.CustomNonbondedForce(ljsoft) force.setNonbondedMethod(original.getNonbondedMethod()) for parameter in ['sigma', 'epsilon']: force.addPerParticleParameter(parameter) for i in range(original.getNumParticles()): _, sigma, epsilon = original.getParticleParameters(i) force.addParticle((sigma, epsilon)) for i in range(original.getNumExclusions()): force.addExclusion(*original.getExclusionParticles(i)) force.setCutoffDistance(original.getCutoffDistance()) force.setUseSwitchingFunction(original.getUseSwitchingFunction()) force.setSwitchingDistance(original.getSwitchingDistance()) if value != 0.0: force.setUseLongRangeCorrection( original.getUseLongRangeCorrection()) for i in range(original.getNumInteractionGroups()): force.addInteractionGroup( *original.getInteractionGroupParameters(i)) force.setForceGroup(index) self._system.addForce(force)
def opls_lj(system, switch_dist): forces = { system.getForce(indx).__class__.__name__: system.getForce(indx) for indx in range(system.getNumForces()) } nonbonded_force = forces['NonbondedForce'] # Custom combining rule lorentz = mm.CustomNonbondedForce( '4*epsilon*((sigma/r)^12-(sigma/r)^6); sigma=sqrt(sigma1*sigma2); epsilon=sqrt(epsilon1*epsilon2)' ) lorentz.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) lorentz.addPerParticleParameter('sigma') lorentz.addPerParticleParameter('epsilon') lorentz.setCutoffDistance(nonbonded_force.getCutoffDistance()) lorentz.setUseSwitchingFunction(True) lorentz.setSwitchingDistance(switch_dist * unit.nanometer) lorentz.setUseLongRangeCorrection(True) system.addForce(lorentz) l_j_set = {} for indx in range(nonbonded_force.getNumParticles()): charge, sigma, epsilon = nonbonded_force.getParticleParameters(indx) l_j_set[indx] = (sigma, epsilon) lorentz.addParticle([sigma, epsilon]) nonbonded_force.setParticleParameters(indx, charge, 0, 0) for indx in range(nonbonded_force.getNumExceptions()): (p1, p2, q, sig, eps) = nonbonded_force.getExceptionParameters(indx) # ALL THE 12, 13 and 14 interactions are EXCLUDED FROM CUSTOM NONBONDED FORCE lorentz.addExclusion(p1, p2) if eps._value > 0: sig14 = (l_j_set[p1][0] * l_j_set[p2][0])**0.5 eps14 = (l_j_set[p1][1] * l_j_set[p2][1])**0.5 nonbonded_force.setExceptionParameters(indx, p1, p2, q, sig14, eps14) return system
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 createUnfoldedSurrogate3(topology, reference_system, locality=5, cutoff=6.0 * unit.angstrom, switch_width=1.0 * unit.angstrom): # Create system deep copy. system = copy.deepcopy(reference_system) # Modify forces as appropriate, copying other forces without modification. forces_to_remove = list() nforces = reference_system.getNumForces() for force_index in range(nforces): reference_force = reference_system.getForce(force_index) force_name = reference_force.__class__.__name__ print force_name if force_name == 'NonbondedForce': forces_to_remove.append(force_index) # Create CustomNonbondedForce instead. energy_expression = "islocal * (E_LJ + E_Coulomb);" energy_expression += "E_LJ = 4*epsilon*((sigma/r)^12 - (sigma/r)^6);" energy_expression += "E_Coulomb = 138.935456*chargeprod/r;" energy_expression += "islocal = step(locality - abs(resid1 - resid2) + 0.001);" custom_force = openmm.CustomNonbondedForce(energy_expression) custom_force.setNonbondedMethod( reference_force.getNonbondedMethod()) # TODO: Fix me custom_force.setCutoffDistance(reference_force.getCutoffDistance()) custom_force.setSwitchingDistance( reference_force.getSwitchingDistance()) custom_force.setUseSwitchingFunction( reference_force.getUseSwitchingFunction()) custom_force.addGlobalParameter("locality", locality) custom_force.addPerParticleParameter("charge") # charge product custom_force.addPerParticleParameter( "sigma") # Lennard-Jones sigma custom_force.addPerParticleParameter( "epsilon") # Lennard-Jones epsilon custom_force.addPerParticleParameter("resid") # residue index system.addForce(custom_force) custom_force.setCutoffDistance(cutoff) custom_force.setNonbondedMethod(custom_force.CutoffNonPeriodic) custom_force.setUseSwitchingFunction(True) custom_force.setSwitchingDistance( custom_force.getCutoffDistance() - switch_width) # Create CustomBondForce energy_expression = "E_LJ + E_Coulomb;" energy_expression += "E_LJ = 4*epsilon*((sigma/r)^12 - (sigma/r)^6);" energy_expression += "E_Coulomb = 138.935456*chargeprod/r;" custom_bond_force = openmm.CustomBondForce(energy_expression) custom_bond_force.addPerBondParameter( "chargeprod") # charge product custom_bond_force.addPerBondParameter( "sigma") # Lennard-Jones sigma custom_bond_force.addPerBondParameter( "epsilon") # Lennard-Jones epsilon system.addForce(custom_bond_force) # Add exceptions for index in range(reference_force.getNumExceptions()): [atom1_index, atom2_index, chargeprod, sigma, epsilon] = reference_force.getExceptionParameters(index) custom_force.addExclusion(atom1_index, atom2_index) custom_bond_force.addBond(atom1_index, atom2_index, [chargeprod, sigma, epsilon]) # Add terms. for atom in topology.atoms(): [charge, sigma, epsilon] = reference_force.getParticleParameters(atom.index) custom_force.addParticle( [charge, sigma, epsilon, atom.residue.index]) elif force_name == 'GBSAOBCForce': #forces_to_remove.append(force_index) reference_force.setNonbondedMethod( reference_force.CutoffNonPeriodic) reference_force.setCutoffDistance(cutoff) # Remove forces scheduled for removal. print("Removing forces:") print(forces_to_remove) for force_index in forces_to_remove[::-1]: system.removeForce(force_index) return system
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) #And define lambda states of interest lambdaVec = np.array(#electrostatic lambda - 1.0 is fully interacting, 0.0 is non-interacting [[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], #LJ lambdas - 1.0 is fully interacting, 0.0 is non-interacting [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] ]) #JUST for boric acid, add a custom bonded force #Couldn't find a nice, compatible force field, but did find A forcefield, so using it #But has no angle terms on O-B-O and instead a weird bond repulsion term #This term also prevents out of plane bending #Simple in our case because boric acid is symmetric, so only need one parameter #Parameters come from Otkidach and Pletnev, 2001 #Here, Ad = (A^2) / (d^6) since Ai and Aj and di and dj are all the same #In the original paper, B-OH bond had A = 1.72 and d = 0.354 #Note that d is dimensionless and A should have units of (Angstrom^3)*(kcal/mol)^(1/2) #These units are inferred just to make things work out with kcal/mol and the given distance dependence bondRepulsionFunction = 'Ad*(1.0/r)^6' BondRepulsionForce = mm.CustomBondForce(bondRepulsionFunction) BondRepulsionForce.addPerBondParameter('Ad') #Units are technically kJ/mol * nm^6 baOxInds = [] for aind in soluteIndices: if top.atoms[aind].type == 'oh': baOxInds.append(aind) for i in range(len(baOxInds)): for j in range(i+1, len(baOxInds)): BondRepulsionForce.addBond(baOxInds[i], baOxInds[j], [0.006289686]) systemRef.addForce(BondRepulsionForce) #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) #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=6000000)
def heteropolymer_SSW( sim_object, interactionMatrix, monomerTypes, extraHardParticlesIdxs, repulsionEnergy=3.0, # base repulsion energy for **all** particles repulsionRadius=1.0, attractionEnergy=3.0, # base attraction energy for **all** particles attractionRadius=1.5, selectiveRepulsionEnergy=20.0, # **extra** repulsive energy for **extraHard** particles selectiveAttractionEnergy=1.0, # **extra** attraction energy that is multiplied by interactionMatrix keepVanishingInteractions=False, name="heteropolymer_SSW", ): """ A version of smooth square well potential that enables the simulation of heteropolymers. Every monomer is assigned a number determining its type, then one can specify additional attraction between the types with the interactionMatrix. Repulsion between all monomers is the same, except for extraHardParticles, which, if specified, have higher repulsion energy. The overall potential is the same as in :py:func:`polychrom.forces.smooth_square_well` Treatment of extraHard particles is the same as in :py:func:`polychrom.forces.selective_SSW` This is an extension of SSW (smooth square well) force in which: a) You can give monomerTypes (e.g. 0, 1, 2 for A, B, C) and interaction strengths between these types. The corresponding entry in interactionMatrix is multiplied by selectiveAttractionEnergy to give the actual **additional** depth of the potential well. b) You can select a subset of particles and make them "extra hard". See selective_SSW force for descrition. Force summary ************* Potential is the same as smooth square well, with the following parameters for particles i and j: * Attraction energy (i,j) = attractionEnergy + selectiveAttractionEnergy * interactionMatrix[i,j] * Repulsion Energy (i,j) = repulsionEnergy + selectiveRepulsionEnergy; if (i) or (j) are extraHard * Repulsion Energy (i,j) = repulsionEnergy; otherwise Parameters ---------- interactionMatrix: np.array the **EXTRA** interaction strenghts between the different types. Only upper triangular values are used. See "Force summary" above monomerTypes: list of int or np.array the type of each monomer, starting at 0 extraHardParticlesIdxs : list of int the list of indices of the "extra hard" particles. The extra hard particles repel all other particles with extra `selectiveRepulsionEnergy` repulsionEnergy: float the heigth of the repulsive part of the potential. E(0) = `repulsionEnergy` repulsionRadius: float the radius of the repulsive part of the potential. E(`repulsionRadius`) = 0, E'(`repulsionRadius`) = 0 attractionEnergy: float the depth of the attractive part of the potential. E(`repulsionRadius`/2 + `attractionRadius`/2) = `attractionEnergy` attractionRadius: float the maximal range of the attractive part of the potential. selectiveRepulsionEnergy: float the **EXTRA** repulsion energy applied to the "extra hard" particles selectiveAttractionEnergy: float the **EXTRA** attraction energy (prefactor for the interactionMatrix interactions) keepVanishingInteractions : bool a flag that determines whether the terms that have zero interaction are still added to the force. This can be useful when changing the force dynamically (i.e. switching interactions on at some point) """ # Check type info for consistency Ntypes = max(monomerTypes) + 1 # IDs should be zero based if any(np.less(interactionMatrix.shape, [Ntypes, Ntypes])): raise ValueError("Need interactions for {0:d} types!".format(Ntypes)) if not np.allclose(interactionMatrix.T, interactionMatrix): raise ValueError("Interaction matrix should be symmetric!") indexpairs = [] for i in range(0, Ntypes): for j in range(0, Ntypes): if (not interactionMatrix[i, j] == 0) or keepVanishingInteractions: indexpairs.append((i, j)) energy = ( "step(REPsigma - r) * Erep + step(r - REPsigma) * Eattr;" "" "Erep = rsc12 * (rsc2 - 1.0) * REPeTot / emin12 + REPeTot;" # + ESlide;" "REPeTot = REPe + (ExtraHard1 + ExtraHard2) * REPeAdd;" "rsc12 = rsc4 * rsc4 * rsc4;" "rsc4 = rsc2 * rsc2;" "rsc2 = rsc * rsc;" "rsc = r / REPsigma * rmin12;" "" "Eattr = - rshft12 * (rshft2 - 1.0) * ATTReTot / emin12 - ATTReTot;" "ATTReTot = ATTRe" ) if len(indexpairs) > 0: energy += ( " + ATTReAdd*(delta(type1-{0:d})*delta(type2-{1:d})" "*INT_{0:d}_{1:d}" ).format(indexpairs[0][0], indexpairs[0][1]) for i, j in indexpairs[1:]: energy += "+delta(type1-{0:d})*delta(type2-{1:d})*INT_{0:d}_{1:d}".format( i, j ) energy += ")" energy += ( ";" "rshft12 = rshft4 * rshft4 * rshft4;" "rshft4 = rshft2 * rshft2;" "rshft2 = rshft * rshft;" "rshft = (r - REPsigma - ATTRdelta) / ATTRdelta * rmin12;" "" ) if selectiveRepulsionEnergy == float("inf"): energy += "REPeAdd = 4 * ((REPsigma / (2.0^(1.0/6.0)) / r)^12 - (REPsigma / (2.0^(1.0/6.0)) / r)^6) + 1;" force = openmm.CustomNonbondedForce(energy) force.name = name force.setCutoffDistance(attractionRadius * sim_object.conlen) force.addGlobalParameter("REPe", repulsionEnergy * sim_object.kT) if selectiveRepulsionEnergy != float("inf"): force.addGlobalParameter("REPeAdd", selectiveRepulsionEnergy * sim_object.kT) force.addGlobalParameter("REPsigma", repulsionRadius * sim_object.conlen) force.addGlobalParameter("ATTRe", attractionEnergy * sim_object.kT) force.addGlobalParameter("ATTReAdd", selectiveAttractionEnergy * sim_object.kT) force.addGlobalParameter( "ATTRdelta", sim_object.conlen * (attractionRadius - repulsionRadius) / 2.0 ) # Coefficients for x^12*(x*x-1) force.addGlobalParameter("emin12", 46656.0 / 823543.0) force.addGlobalParameter("rmin12", np.sqrt(6.0 / 7.0)) for i, j in indexpairs: force.addGlobalParameter( "INT_{0:d}_{1:d}".format(i, j), interactionMatrix[i, j] ) force.addPerParticleParameter("type") force.addPerParticleParameter("ExtraHard") for i in range(sim_object.N): force.addParticle((float(monomerTypes[i]), float(i in extraHardParticlesIdxs))) return force
def selective_SSW( sim_object, stickyParticlesIdxs, extraHardParticlesIdxs, repulsionEnergy=3.0, # base repulsion energy for **all** particles repulsionRadius=1.0, attractionEnergy=3.0, # base attraction energy for **all** particles attractionRadius=1.5, selectiveRepulsionEnergy=20.0, # **extra** repulsive energy for **extraHard** particles selectiveAttractionEnergy=1.0, # **extra** attractive energy for **sticky** particles name="selective_SSW", ): """ This is a simple and fast polynomial force that looks like a smoothed version of the square-well potential. The energy equals `repulsionEnergy` around r=0, stays flat until 0.6-0.7, then drops to zero together with its first derivative at r=1.0. After that it drop down to `attractionEnergy` and gets back to zero at r=`attractionRadius`. The energy function is based on polynomials of 12th power. Both the function and its first derivative is continuous everywhere within its domain and they both get to zero at the boundary. This is a tunable version of SSW: a) You can specify the set of "sticky" particles. The sticky particles are attracted only to other sticky particles. b) You can **smultaneously** select a subset of particles and make them "extra hard". This force was used two-ways. First was to make a small subset of particles very sticky. In that case, it is advantageous to make the sticky particles and their neighbours "extra hard" and thus prevent the system from collapsing. Another useage is to induce phase separation by making all B monomers sticky. In that case, extraHard particles may not be needed at all, because the system would not collapse on iteslf. Parameters ---------- stickyParticlesIdxs: list of int the list of indices of the "sticky" particles. The sticky particles are attracted to each other with extra `selectiveAttractionEnergy` extraHardParticlesIdxs : list of int the list of indices of the "extra hard" particles. The extra hard particles repel all other particles with extra `selectiveRepulsionEnergy` repulsionEnergy: float the heigth of the repulsive part of the potential. E(0) = `repulsionEnergy` repulsionRadius: float the radius of the repulsive part of the potential. E(`repulsionRadius`) = 0, E'(`repulsionRadius`) = 0 attractionEnergy: float the depth of the attractive part of the potential. E(`repulsionRadius`/2 + `attractionRadius`/2) = `attractionEnergy` attractionRadius: float the maximal range of the attractive part of the potential. selectiveRepulsionEnergy: float the **EXTRA** repulsion energy applied to the **extra hard** particles selectiveAttractionEnergy: float the **EXTRA** attraction energy applied to the **sticky** particles """ energy = ( "step(REPsigma - r) * Erep + step(r - REPsigma) * Eattr;" "" "Erep = rsc12 * (rsc2 - 1.0) * REPeTot / emin12 + REPeTot;" # + ESlide;" "REPeTot = REPe + (ExtraHard1 + ExtraHard2) * REPeAdd;" "rsc12 = rsc4 * rsc4 * rsc4;" "rsc4 = rsc2 * rsc2;" "rsc2 = rsc * rsc;" "rsc = r / REPsigma * rmin12;" "" "Eattr = - rshft12 * (rshft2 - 1.0) * ATTReTot / emin12 - ATTReTot;" "ATTReTot = ATTRe + min(Sticky1, Sticky2) * ATTReAdd;" "rshft12 = rshft4 * rshft4 * rshft4;" "rshft4 = rshft2 * rshft2;" "rshft2 = rshft * rshft;" "rshft = (r - REPsigma - ATTRdelta) / ATTRdelta * rmin12;" "" ) if selectiveRepulsionEnergy == float("inf"): energy += "REPeAdd = 4 * ((REPsigma / (2.0^(1.0/6.0)) / r)^12 - (REPsigma / (2.0^(1.0/6.0)) / r)^6) + 1;" force = openmm.CustomNonbondedForce(energy) force.name = name force.setCutoffDistance(attractionRadius * sim_object.conlen) force.addGlobalParameter("REPe", repulsionEnergy * sim_object.kT) if selectiveRepulsionEnergy != float("inf"): force.addGlobalParameter("REPeAdd", selectiveRepulsionEnergy * sim_object.kT) force.addGlobalParameter("REPsigma", repulsionRadius * sim_object.conlen) force.addGlobalParameter("ATTRe", attractionEnergy * sim_object.kT) force.addGlobalParameter("ATTReAdd", selectiveAttractionEnergy * sim_object.kT) force.addGlobalParameter( "ATTRdelta", sim_object.conlen * (attractionRadius - repulsionRadius) / 2.0 ) # Coefficients for x^12*(x*x-1) force.addGlobalParameter("emin12", 46656.0 / 823543.0) force.addGlobalParameter("rmin12", np.sqrt(6.0 / 7.0)) force.addPerParticleParameter("Sticky") force.addPerParticleParameter("ExtraHard") counts = np.bincount(stickyParticlesIdxs, minlength=sim_object.N) for i in range(sim_object.N): force.addParticle((float(counts[i]), float(i in extraHardParticlesIdxs))) return force
def smooth_square_well( sim_object, repulsionEnergy=3.0, repulsionRadius=1.0, attractionEnergy=0.5, attractionRadius=2.0, name="smooth_square_well", ): """ This is a simple and fast polynomial force that looks like a smoothed version of the square-well potential. The energy equals `repulsionEnergy` around r=0, stays flat until 0.6-0.7, then drops to zero together with its first derivative at r=1.0. After that it drop down to `attractionEnergy` and gets back to zero at r=`attractionRadius`. The energy function is based on polynomials of 12th power. Both the function and its first derivative is continuous everywhere within its domain and they both get to zero at the boundary. Parameters ---------- repulsionEnergy: float the heigth of the repulsive part of the potential. E(0) = `repulsionEnergy` repulsionRadius: float the radius of the repulsive part of the potential. E(`repulsionRadius`) = 0, E'(`repulsionRadius`) = 0 attractionEnergy: float the depth of the attractive part of the potential. E(`repulsionRadius`/2 + `attractionRadius`/2) = `attractionEnergy` attractionRadius: float the radius of the attractive part of the potential. E(`attractionRadius`) = 0, E'(`attractionRadius`) = 0 """ nbCutOffDist = sim_object.conlen * attractionRadius energy = ( "step(REPsigma - r) * Erep + step(r - REPsigma) * Eattr;" "" "Erep = rsc12 * (rsc2 - 1.0) * REPe / emin12 + REPe;" "rsc12 = rsc4 * rsc4 * rsc4;" "rsc4 = rsc2 * rsc2;" "rsc2 = rsc * rsc;" "rsc = r / REPsigma * rmin12;" "" "Eattr = - rshft12 * (rshft2 - 1.0) * ATTRe / emin12 - ATTRe;" "rshft12 = rshft4 * rshft4 * rshft4;" "rshft4 = rshft2 * rshft2;" "rshft2 = rshft * rshft;" "rshft = (r - REPsigma - ATTRdelta) / ATTRdelta * rmin12" ) force = openmm.CustomNonbondedForce(energy) force.name = name force.addGlobalParameter("REPe", repulsionEnergy * sim_object.kT) force.addGlobalParameter("REPsigma", repulsionRadius * sim_object.conlen) force.addGlobalParameter("ATTRe", attractionEnergy * sim_object.kT) force.addGlobalParameter( "ATTRdelta", sim_object.conlen * (attractionRadius - repulsionRadius) / 2.0 ) # Coefficients for the minimum of x^12*(x*x-1) force.addGlobalParameter("emin12", 46656.0 / 823543.0) force.addGlobalParameter("rmin12", np.sqrt(6.0 / 7.0)) for _ in range(sim_object.N): force.addParticle(()) force.setCutoffDistance(nbCutOffDist) return force
def create_alchemical_intermediates(reference_system, bond_atoms, bond_lambda, kT, annihilate=False): """ Build alchemically-modified system where ligand is decoupled or annihilated using Custom*Force classes. ARGUMENTS reference_system (simtk.openmm.System) - reference System object from which alchemical derivatives will be made (will not be modified) bond_atoms (list of int) - atoms spanning bond to be eliminated bond_lambda (float) - lambda value for bond breaking (lambda = 1 is original system, lambda = 0 is broken-bond system) kT (simtk.unit.Quantity with units compatible with simtk.unit.kilocalories_per_mole) - thermal energy, used in constructing alchemical intermediates RETURNS system (simtk.openmm.System) - alchemical intermediate copy """ # Create new system. system = openmm.System() # Set periodic box vectors. [a, b, c] = reference_system.getDefaultPeriodicBoxVectors() system.setDefaultPeriodicBoxVectors(a, b, c) # Add atoms. for atom_index in range(reference_system.getNumParticles()): mass = reference_system.getParticleMass(atom_index) system.addParticle(mass) # Add constraints for constraint_index in range(reference_system.getNumConstraints()): [iatom, jatom, r0] = reference_system.getConstraintParameters(constraint_index) # Raise an exception if the specified bond_atoms are part of a constrained bond; we can't handle that. if (iatom in bond_atoms) and (jatom in bond_atoms): raise Exception("Bond to be broken is part of a constraint.") system.addConstraint(iatom, jatom, r0) # Perturb force terms. for force_index in range(reference_system.getNumForces()): # Dispatch forces based on reference force type. reference_force = reference_system.getForce(force_index) if bond_lambda == 1.0: # Just make a copy of the force if lambda = 1. force = copy.deepcopy(reference_force) system.addForce(force) continue if isinstance(reference_force, openmm.HarmonicBondForce): force = openmm.HarmonicBondForce() for bond_index in range(reference_force.getNumBonds()): # Retrieve parameters. [iatom, jatom, r0, K] = reference_force.getBondParameters(bond_index) if (iatom in bond_atoms) and (jatom in bond_atoms): if bond_lambda == 0.0: continue # eliminate this bond if broken # Replace this bond with a soft-core (Morse) bond. softcore_bond_force = create_softcore_bond( iatom, jatom, r0, K, kT, bond_lambda) system.addForce(softcore_bond_force) else: # Add bond parameters. force.addBond(iatom, jatom, r0, K) # Add force to new system. system.addForce(force) elif isinstance(reference_force, openmm.HarmonicAngleForce): force = openmm.HarmonicAngleForce() for angle_index in range(reference_force.getNumAngles()): # Retrieve parameters. [iatom, jatom, katom, theta0, Ktheta] = reference_force.getAngleParameters(angle_index) # Turn off angle terms that span bond. if ((iatom in bond_atoms) and (jatom in bond_atoms)) or ((jatom in bond_atoms) and (katom in bond_atoms)): if bond_lambda == 0.0: continue # eliminate this angle if bond broken Ktheta *= bond_lambda # Add parameters. force.addAngle(iatom, jatom, katom, theta0, Ktheta) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.PeriodicTorsionForce): force = openmm.PeriodicTorsionForce() for torsion_index in range(reference_force.getNumTorsions()): # Retrieve parmaeters. [ particle1, particle2, particle3, particle4, periodicity, phase, k ] = reference_force.getTorsionParameters(torsion_index) # Annihilate if torsion spans bond. if ((particle1 in bond_atoms) and (particle2 in bond_atoms)) or ( (particle2 in bond_atoms) and (particle3 in bond_atoms)) or ( (particle3 in bond_atoms) and (particle4 in bond_atoms)): if bond_lambda == 0.0: continue # eliminate this torsion if bond broken k *= bond_lambda # Add parameters. force.addTorsion(particle1, particle2, particle3, particle4, periodicity, phase, k) # Add force to system. system.addForce(force) elif isinstance(reference_force, openmm.NonbondedForce): # NonbondedForce will handle charges and exception interactions. force = openmm.NonbondedForce() for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon ] = reference_force.getParticleParameters(particle_index) # Lennard-Jones and electrostatic interactions involving atoms in bond will be handled by CustomNonbondedForce except at lambda = 0 or 1. if ((bond_lambda > 0) and (bond_lambda < 1)) and (particle_index in bond_atoms): # TODO: We have to also add softcore electrostatics. epsilon *= 0.0 # Add modified particle parameters. force.addParticle(charge, sigma, epsilon) for exception_index in range(reference_force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon ] = reference_force.getExceptionParameters(exception_index) # Modify exception for bond atoms. if ((iatom in bond_atoms) and (jatom in bond_atoms)): if (bond_lambda == 0.0): continue # Omit exception if bond has been turned off. # Alchemically modify epsilon and chargeprod. if (iatom in bond_atoms) and (jatom in bond_atoms): # Attenuate exception interaction (since it will be covered by CustomNonbondedForce interactions). epsilon *= bond_lambda chargeprod *= bond_lambda # TODO: Compute restored (1,3) and (1,4) interactions across modified bond. # Add modified exception parameters. force.addException(iatom, jatom, chargeprod, sigma, epsilon) # Set parameters. force.setNonbondedMethod(reference_force.getNonbondedMethod()) force.setCutoffDistance(reference_force.getCutoffDistance()) force.setReactionFieldDielectric( reference_force.getReactionFieldDielectric()) force.setEwaldErrorTolerance( reference_force.getEwaldErrorTolerance()) # Add force to new system. system.addForce(force) if (bond_lambda == 0.0) or (bond_lambda == 1.0): continue # don't need softcore if bond is turned off # CustomNonbondedForce will handle the softcore interactions with and among alchemically-modified atoms. # Softcore potential. # TODO: Add coulomb interaction. energy_expression = "4*epsilon*compute*x*(x-1.0);" energy_expression += "x = 1.0/(alpha*(bond_lambda*(1.0-bond_lambda)/0.25) + (r/sigma)^6);" energy_expression += "epsilon = sqrt(epsilon1*epsilon2);" energy_expression += "sigma = 0.5*(sigma1 + sigma2);" energy_expression += "compute = (1-bond_lambda)*alchemical1*alchemical2 + (alchemical1*(1-alchemical2) + (1-alchemical1)*alchemical2);" # only compute interactions with or between alchemically-modified atoms force = openmm.CustomNonbondedForce(energy_expression) alpha = 0.5 # softcore parameter force.addGlobalParameter("alpha", alpha) force.addGlobalParameter("bond_lambda", bond_lambda) force.addPerParticleParameter("charge") force.addPerParticleParameter("sigma") force.addPerParticleParameter("epsilon") force.addPerParticleParameter("alchemical") for particle_index in range(reference_force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon ] = reference_force.getParticleParameters(particle_index) # Alchemically modify parameters. if particle_index in bond_atoms: force.addParticle([charge, sigma, epsilon, 1]) else: force.addParticle([charge, sigma, epsilon, 0]) for exception_index in range(reference_force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon ] = reference_force.getExceptionParameters(exception_index) # Exclude exception for bonded atoms. if (iatom in bond_atoms) and (jatom in bond_atoms): continue # All exceptions are handled by NonbondedForce, so we exclude all these here. force.addExclusion(iatom, jatom) if reference_force.getNonbondedMethod() in [ openmm.NonbondedForce.Ewald, openmm.NonbondedForce.PME ]: force.setNonbondedMethod( openmm.CustomNonbondedForce.CutoffPeriodic) else: force.setNonbondedMethod(reference_force.getNonbondedMethod()) force.setCutoffDistance(reference_force.getCutoffDistance()) system.addForce(force) else: # Add copy of force term. force = copy.deepcopy(reference_force) system.addForce(force) return system