def grosberg_polymer_bonds(sim_object, bonds, k=30, name="grosberg_polymer"): """Adds FENE bonds according to Halverson-Grosberg 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.) This method has a repulsive potential build-in, so that Grosberg bonds could be used with truncated potentials. Is of no use unless you really need to simulate Grosberg-type system. Parameters ---------- k : float, optional Arbitrary parameter; default value as in Grosberg paper. """ equation = "- 0.5 * k * r0 * r0 * log(1-(r/r0)* (r / r0))" force = openmm.CustomBondForce(equation) force.name = name force.addGlobalParameter( "k", k * sim_object.kT / (sim_object.conlen * sim_object.conlen)) force.addGlobalParameter("r0", sim_object.conlen * 1.5) for bond_idx, (i, j) in enumerate(bonds): if (i >= sim_object.N) or (j >= sim_object.N): raise ValueError("\nCannot add bond with monomers %d,%d that" "are beyound the polymer length %d" % (i, j, sim_object.N)) force.addBond(int(i), int(j)) return force
def applyLigandChunkRestraint(system, k2, k3, R2, R3, R4, indexOfAffectedAtoms): forceConstant_k2 = u.Quantity(value=k2, unit=u.kilocalorie / (u.mole * u.angstrom * u.angstrom)) forceConstant_k3 = u.Quantity(value=k3, unit=u.kilocalorie / (u.mole * u.angstrom * u.angstrom)) restraint_force = mm.CustomBondForce( 'step(R2 - r) * f1 + step(r - R3) * select( step(r - R4), f3, f2);' 'f1 = k2 * (r - R2)^2;' 'f2 = k3 * (r - R3)^2;' 'f3 = k3 * (R4 - R3) * (2 * r - R4 - R3)') restraint_force.addGlobalParameter( "k2", forceConstant_k2.in_units_of(u.kilojoule / (u.mole * u.nanometer * u.nanometer))) restraint_force.addGlobalParameter( "k3", forceConstant_k3.in_units_of(u.kilojoule / (u.mole * u.nanometer * u.nanometer))) restraint_force.addGlobalParameter("R2", R2) restraint_force.addGlobalParameter("R3", R3) restraint_force.addGlobalParameter("R4", R4) restraint_force.addBond(indexOfAffectedAtoms[0], indexOfAffectedAtoms[1]) system.addForce(restraint_force)
def test_restorable_openmm_object_failure(self): """An exception is raised if the class has a restorable hash but the class can't be found.""" force = openmm.CustomBondForce('0.0') force_hash_parameter_name = self.dummy_force._hash_parameter_name force.addGlobalParameter(force_hash_parameter_name, 15.0) with nose.tools.assert_raises(RestorableOpenMMObjectError): RestorableOpenMMObject.restore_interface(force)
def _create_restraint_force(self, particle1, particle2): """ Create a new restraint force between specified atoms. Parameters ---------- particle1 : int Index of first atom in restraint particle2 : int Index of second atom in restraint Returns ------- force : simtk.openmm.CustomBondForce The created restraint force. """ force = openmm.CustomBondForce(self._energy_function) force.addGlobalParameter('lambda_restraints', 1.0) is_periodic = self._system.usesPeriodicBoundaryConditions() try: # This was added in OpenMM 7.1 force.setUsesPeriodicBoundaryConditions(is_periodic) except AttributeError: pass for parameter in self._bond_parameter_names: force.addPerBondParameter(parameter) try: force.addBond(particle1, particle2, self._bond_parameters) except Exception as e: print('particle1: %s' % str(particle1)) print('particle2: %s' % str(particle1)) print('bond_parameters: %s' % str(self._bond_parameters)) raise(e) return force
def _createRestraintForce(self, particle1, particle2, mm=None): """ Create a new copy of the receptor-ligand restraint force. Parameters ---------- particle1 : int Index of first particle for which restraint is to be applied. particle2 : int Index of second particle for which restraint is to be applied mm : simtk.openmm compliant interface, optional, default=None If specified, use an alternative OpenMM API implementation. Otherwise, use simtk.openmm. Returns ------- force : simtk.openmm.CustomBondForce A restraint force object """ if mm is None: mm = openmm force = openmm.CustomBondForce(self.energy_function) force.addGlobalParameter('lambda_restraints', 1.0) for parameter in self.bond_parameter_names: force.addPerBondParameter(parameter) force.addBond(particle1, particle2, self.bond_parameters) return force
def test_restorable_openmm_object(self): """Test RestorableOpenMMObject classes can be serialized and copied correctly.""" # Each test case is a pair (object, is_restorable). test_cases = [(copy.deepcopy(self.dummy_force), True), (copy.deepcopy(self.dummy_integrator), True), (openmm.CustomBondForce('K'), False)] for openmm_object, is_restorable in test_cases: assert RestorableOpenMMObject.is_restorable( openmm_object) is is_restorable err_msg = '{}: {}, {}'.format( openmm_object, RestorableOpenMMObject.restore_interface(openmm_object), is_restorable) assert RestorableOpenMMObject.restore_interface( openmm_object) is is_restorable, err_msg # Serializing/deserializing restore the class correctly. serialization = openmm.XmlSerializer.serialize(openmm_object) deserialized_object = RestorableOpenMMObject.deserialize_xml( serialization) if is_restorable: assert type(deserialized_object) is type(openmm_object) # Copying keep the Python class and attributes. deserialized_object._monkey_patching = True copied_object = copy.deepcopy(deserialized_object) if is_restorable: assert type(copied_object) is type(openmm_object) assert hasattr(copied_object, '_monkey_patching')
def addLigandBox(topology, positions, system, resname, dummy, radius, worker): masses = [] coords = np.ndarray(shape=(0, 3)) ligand_atoms = [] for atom in topology.atoms(): if atom.residue.name == resname and atom.element.symbol != "H": masses.append(atom.element.mass.value_in_unit(unit=unit.dalton)) coords = np.vstack( (coords, positions[atom.index].value_in_unit(unit=unit.nanometer))) ligand_atoms.append(atom) masses = np.array(masses) masses /= masses.sum() mass_center = coords.astype('float64').T.dot(masses) atomClosestToMassCenter = min( ligand_atoms, key=lambda x: np.linalg.norm(mass_center - positions[x.index]. value_in_unit(unit=unit.nanometer))) if worker == 0: utilities.print_unbuffered( "Ligand atom selected to check distance to the box", atomClosestToMassCenter.residue.name, atomClosestToMassCenter.name, atomClosestToMassCenter.index) ligand_atom = atomClosestToMassCenter.index forceFB = mm.CustomBondForce('step(r-r0)*(k_box/2) * (r-r0)^2') forceFB.addPerBondParameter("k_box") forceFB.addPerBondParameter("r0") forceFB.addBond(dummy, ligand_atom, [ 5.0 * unit.kilocalories_per_mole / unit.angstroms**2, radius * unit.angstroms ]) system.addForce(forceFB)
def FENE_bonds( sim_object, bonds, bondWiggleDistance=0.05, bondLength=1.0, name="FENE_bonds", override_checks=False, ): """Adds harmonic bonds Parameters ---------- bonds : iterable of (int, int) Pairs of particle indices to be connected with a bond. bondWiggleDistance : float Average displacement from the equilibrium bond distance. Can be provided per-particle. bondLength : float The length of the bond. Can be provided per-particle. override_checks: bool If True then do not check that no bonds are repeated. False by default. """ # check for repeated bonds if not override_checks: _check_bonds(bonds, sim_object.N) energy = (f"(1. / wiggle) * univK * " f"(sqrt((r-r0 * conlen)* " f" (r - r0 * conlen) + a * a) - a)") force = openmm.CustomBondForce(energy) force.name = name force.addPerBondParameter("wiggle") force.addPerBondParameter("r0") force.addGlobalParameter("univK", sim_object.kT / sim_object.conlen) force.addGlobalParameter("a", 0.02 * sim_object.conlen) force.addGlobalParameter("conlen", sim_object.conlen) bondLength = _to_array_1d(bondLength, len(bonds)) * sim_object.length_scale bondWiggleDistance = (_to_array_1d(bondWiggleDistance, len(bonds)) * sim_object.length_scale) for bond_idx, (i, j) in enumerate(bonds): if (i >= sim_object.N) or (j >= sim_object.N): raise ValueError("\nCannot add bond with monomers %d,%d that" "are beyound the polymer length %d" % (i, j, sim_object.N)) force.addBond( int(i), int(j), [float(bondWiggleDistance[bond_idx]), float(bondLength[bond_idx])], ) return force
def add_harmonic_restraints(system: mm.System, args: ListOfArgs): """Restraints format is different here than in SM webservice. Example record: :10 :151\n or :10 :151 0.1 30000\n :i :j distance energy """ print(" Adding harmonic restraints") if args.HR_USE_FLAT_BOTTOM_FORCE: contact_force = mm.CustomBondForce('step(r-r0) * (k/2) * (r-r0)^2') contact_force.addPerBondParameter('r0') contact_force.addPerBondParameter('k') else: contact_force = mm.HarmonicBondForce() system.addForce(contact_force) with open(args.HR_RESTRAINTS_PATH) as input_file: counter = 0 for line in input_file: columns = line.split() atom_index_i = int(columns[0][1:]) - 1 atom_index_j = int(columns[1][1:]) - 1 try: r0 = float(columns[2]) k = float(columns[3]) * args.HR_K_SCALE except IndexError: r0 = args.HR_R0_PARAM k = args.HR_K_PARAM if args.HR_USE_FLAT_BOTTOM_FORCE: contact_force.addBond(atom_index_i, atom_index_j, [r0, k]) else: contact_force.addBond(atom_index_i, atom_index_j, r0, k) counter += 1 print(f" {counter} restraints added.")
def __init__(self, group): RgSq = openmm.CustomBondForce('r^2') RgSq.setUsesPeriodicBoundaryConditions(False) for i, j in itertools.combinations(group, 2): RgSq.addBond(i, j) super().__init__(f'sqrt(RgSq)/{len(group)}') self.addCollectiveVariable('RgSq', RgSq)
def test_parsing(self, ethane_system_topology): custom_bond = openmm.CustomBondForce('a+b*r') custom_bond.addPerBondParameter('a') custom_bond.addPerBondParameter('b') custom_bond.addBond(0, 1, (10, 20)) my_ommp = Ommperator(ethane_system_topology[0], ethane_system_topology[1]) my_bond_ommp = CustomBondForceOmmperator(my_ommp, custom_bond, 0) assert my_bond_ommp.particle1 == custom_bond.getBondParameters(0)[0] assert my_bond_ommp.particle2 == custom_bond.getBondParameters(0)[1] assert my_bond_ommp.parameters == custom_bond.getBondParameters(0)[2]
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 _add_bond_force_terms(self): core_energy_expression = '(K/2)*(r-length)^2;' 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.CustomBondForce(core_energy_expression) custom_core_force.addPerBondParameter('length') custom_core_force.addPerBondParameter('k') custom_core_force.addPerBondParameter('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 WCADimerVacuum(mm=None, mass=mass, epsilon=epsilon, sigma=sigma, h=h, r0=r0, w=w): """ Create a bistable dimer. OPTIONAL ARGUMENTS """ # Choose OpenMM package. if mm is None: mm = openmm # Create system system = mm.System() # Add particles to system. for n in range(2): system.addParticle(mass) # Add dimer potential to first two particles. dimer_force = openmm.CustomBondForce('h*(1-((r-r0-w)/w)^2)^2;') dimer_force.addGlobalParameter('h', h) # barrier height dimer_force.addGlobalParameter('r0', r0) # compact state separation dimer_force.addGlobalParameter('w', w) # second minimum is at r0 + 2*w dimer_force.addBond(0, 1, []) system.addForce(dimer_force) # Create initial coordinates using random positions. coordinates = units.Quantity(numpy.zeros([2, 3], numpy.float64), units.nanometer) # Reposition dimer particles at compact minimum. coordinates[0, :] *= 0.0 coordinates[1, :] *= 0.0 coordinates[1, 0] = r0 # Return system and coordinates. return [system, coordinates]
def _createRestraintForce(self, particle1, particle2, mm=None): """ Create a new copy of the receptor-ligand restraint force. RETURNS force (simtk.openmm.CustomBondForce) - a restraint force object """ if mm is None: mm = openmm force = openmm.CustomBondForce(self.energy_function) force.addGlobalParameter('restraint_lambda', 1.0) for parameter in self.bond_parameter_names: force.addPerBondParameter(parameter) force.addBond(particle1, particle2, self.bond_parameters) return force
def modify_harmonic_bonds(self): """ turn the harmonic bonds into a custom bond force lambda_protocol : - 'lambda_MM_bonds' : 1 -> 0 - 'lambda_scale' : beta / beta0 """ self._alchemical_to_old_bonds = {} bond_expression = 'lambda_MM_bonds * lambda_scale * (k/2)*(r-r0)^2;' custom_bond_force = openmm.CustomBondForce(bond_expression) #add the global params custom_bond_force.addGlobalParameter('lambda_MM_bonds', 1.) custom_bond_force.addGlobalParameter('lambda_scale', 1.) #add the perbondparams custom_bond_force.addPerBondParameter('r0') custom_bond_force.addPerBondParameter('k') #now to iterate over the bonds. for idx in range(self._system_forces['HarmonicBondForce'].getNumBonds()): p1, p2, length, k = self._system_forces['HarmonicBondForce'].getBondParameters(idx) if self.is_in_alchemical_region({p1, p2}): #then this bond is in the transforming residue #first thing to do is to zero the force from the `HarmonicBondForce` self._system_forces['HarmonicBondForce'].setBondParameters(idx, p1, p2, length, k*0.0) self._endstate_system_forces['HarmonicBondForce'].setBondParameters(idx, p1, p2, length, k*0.0) #for bookkeeping #then add it to the custom bond force custom_bond_idx = custom_bond_force.addBond(p1, p2, [length, k]) #add to the alchemical bonds dict for bookkeeping self._alchemical_to_old_bonds[custom_bond_idx] = idx #then add the custom bond force to the system if self._system_forces['HarmonicBondForce'].usesPeriodicBoundaryConditions(): custom_bond_force.setUsesPeriodicBoundaryConditions(True) self._system.addForce(custom_bond_force)
def get_custom_bond_force(self): """ make a custom bond force object """ from openmmtools.constants import ONE_4PI_EPS0 # constant for coulomb (implicitly in md_unit_system units) custom_expression = "lambda_scale * (U_electrostatics + U_sterics);" #name the energy contributions custom_expression += "U_sterics = 4*epsilon*x*(x-1.0); x = (sigma/reff_sterics)^6;" #name sterics expression #custom_expression += "reff_sterics = r;" custom_expression += "reff_sterics = sigma*((softcore_alpha_sterics * lambda_nonbonded_MM_sterics + (r/sigma)^6))^(1/6);" # effective softcore distance for sterics custom_expression += "epsilon = (1 - lambda_nonbonded_MM_sterics) * epsilonA + lambda_nonbonded_MM_sterics * epsilonB;" custom_expression += "sigma = (1 - lambda_nonbonded_MM_sterics) * sigmaA + lambda_nonbonded_MM_sterics * sigmaB;" custom_expression += "U_electrostatics = chargeProd * ONE_4PI_EPS0 / reff_electrostatics;" custom_expression += f"ONE_4PI_EPS0 = {ONE_4PI_EPS0};" #custom_expression += f"reff_electrostatics = r;" custom_expression += "reff_electrostatics = ((softcore_alpha_electrostatics * lambda_nonbonded_MM_electrostatics + (r)^6))^(1/6);" custom_expression += "chargeProd = (1 - lambda_nonbonded_MM_electrostatics) * chargeProdA + lambda_nonbonded_MM_electrostatics * chargeProdB;" custom_expression += f"softcore_alpha_sterics = {self._softcore_alpha_sterics};" custom_expression += f"softcore_alpha_electrostatics = {self._softcore_alpha_electrostatics};" custom_bond_force = openmm.CustomBondForce(custom_expression) global_params = ['lambda_scale','lambda_nonbonded_MM_sterics', 'lambda_nonbonded_MM_electrostatics'] per_bond_params = ['epsilonA', 'epsilonB', 'sigmaA', 'sigmaB', 'chargeProdA', 'chargeProdB'] for global_param in global_params: if global_param == 'lambda_scale': # 'lambda_scale' is the exception; it defaults to 0.0 custom_bond_force.addGlobalParameter(global_param, 1.) else: custom_bond_force.addGlobalParameter(global_param, 0.) for per_bond_param in per_bond_params: custom_bond_force.addPerBondParameter(per_bond_param) self._system.addForce(custom_bond_force) return custom_bond_force
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 createUnfoldedSurrogate2(topology, reference_system, locality=5): # 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 CustomBondForce instead. energy_expression = "E_LJ + E_Coulomb + E_GB;" energy_expression += "E_LJ = 4*epsilon*((sigma/r)^12 - (sigma/r)^6);" energy_expression += "E_Coulomb = 138.935456*chargeprod/r;" energy_expression += "E_GB = -0.5*(1/epsilon_solute - 1/epsilon_solvent)*chargeprod/f_GB;" energy_expression += "f_GB = (r^2 + RiRj*exp(-r/(4*RiRj)))^0.5;" energy_expression += "RiRj = 0.25;" 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 custom_bond_force.addGlobalParameter("epsilon_solvent", 78.5) custom_bond_force.addGlobalParameter("epsilon_solute", 1) system.addForce(custom_bond_force) # Add exclusions. print("Building exclusions...") from sets import Set exceptions = Set() for index in range(reference_force.getNumExceptions()): [atom1_index, atom2_index, chargeprod, sigma, epsilon] = reference_force.getExceptionParameters(index) custom_bond_force.addBond(atom1_index, atom2_index, [chargeprod, sigma, epsilon]) if atom2_index < atom1_index: exceptions.add((atom2_index, atom1_index)) else: exceptions.add((atom1_index, atom2_index)) # Add local interactions. print("Adding local interactions...") for atom1 in topology.atoms(): [charge1, sigma1, epsilon1] = reference_force.getParticleParameters(atom1.index) for atom2 in topology.atoms(): if (atom1.index < atom2.index) and ( abs(atom1.residue.index - atom2.residue.index) <= locality) and ((atom1.index, atom2.index) not in exceptions): [charge2, sigma2, epsilon2 ] = reference_force.getParticleParameters(atom1.index) chargeprod = charge1 * charge2 sigma = 0.5 * (sigma1 + sigma2) epsilon = unit.sqrt(epsilon1 * epsilon2) custom_bond_force.addBond(atom1.index, atom2.index, [chargeprod, sigma, epsilon]) print("%d custom bond terms added." % custom_bond_force.getNumBonds()) elif force_name == 'GBSAOBCForce': forces_to_remove.append(force_index) # 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 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) #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 solOxInds = [] for solInds in soluteIndices: baOxInds = [] for aind in solInds: if top.atoms[aind].type == 'oh': baOxInds.append(aind) solOxInds.append(baOxInds) for baOxInds in solOxInds: 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, verbose=True) #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) #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_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])
def constant_force_bonds( sim_object, bonds, bondWiggleDistance=0.05, bondLength=1.0, quadraticPart = 0.02, name="abs_bonds", override_checks=False, ): """ Constant force bond force. Energy is roughly linear with estension after r=quadraticPart; before it is quadratic to make sure the force is differentiable. Force is parametrized using the same approach as bond force: it reaches U=kT at extension = bondWiggleDistance Note that, just as with bondForce, mean squared extension is actually larger than wiggleDistance by sqrt(2) factor. Parameters ---------- bonds : iterable of (int, int) Pairs of particle indices to be connected with a bond. bondWiggleDistance : float Displacement at which bond energy equals 1 kT. Can be provided per-particle. bondLength : float The length of the bond. Can be provided per-particle. override_checks: bool If True then do not check that no bonds are repeated. False by default. """ # check for repeated bonds if not override_checks: _check_bonds(bonds, sim_object.N) energy = ( f"(1. / wiggle) * univK * " f"(sqrt((r-r0 * conlen)* " f" (r - r0 * conlen) + a * a) - a)" ) force = openmm.CustomBondForce(energy) force.name = name force.addPerBondParameter("wiggle") force.addPerBondParameter("r0") force.addGlobalParameter("univK", sim_object.kT / sim_object.conlen) force.addGlobalParameter("a", quadraticPart * sim_object.conlen) force.addGlobalParameter("conlen", sim_object.conlen) bondLength = _to_array_1d(bondLength, len(bonds)) * sim_object.length_scale bondWiggleDistance = ( _to_array_1d(bondWiggleDistance, len(bonds)) * sim_object.length_scale ) for bond_idx, (i, j) in enumerate(bonds): if (i >= sim_object.N) or (j >= sim_object.N): raise ValueError( "\nCannot add bond with monomers %d,%d that" "are beyound the polymer length %d" % (i, j, sim_object.N) ) force.addBond( int(i), int(j), [float(bondWiggleDistance[bond_idx]), float(bondLength[bond_idx])], ) return force
def _process_nonbonded_forces(openff_sys, openmm_sys, combine_nonbonded_forces=False): """Process the vdW and Electrostatics sections of an OpenFF Interchange into a corresponding openmm.NonbondedForce or a collection of other forces (NonbondedForce, CustomNonbondedForce, CustomBondForce)""" if "vdW" in openff_sys.handlers: vdw_handler = openff_sys.handlers["vdW"] vdw_cutoff = vdw_handler.cutoff.m_as(off_unit.angstrom) * unit.angstrom vdw_method = vdw_handler.method.lower() electrostatics_handler = openff_sys.handlers["Electrostatics"] electrostatics_method = electrostatics_handler.method.lower() if vdw_handler.mixing_rule != "lorentz-berthelot": if combine_nonbonded_forces: raise UnsupportedExportError( "OpenMM's default NonbondedForce only supports Lorentz-Berthelot mixing rules." "Try setting `combine_nonbonded_forces=False`.") else: raise NotImplementedError( f"Mixing rule `{vdw_handler.mixing_rule}` not compatible with current OpenMM export." "The only supported values is `lorentez-berthelot`.") if vdw_handler.mixing_rule == "lorentz-berthelot": if not combine_nonbonded_forces: mixing_rule_expression = ( "sigma=(sigma1+sigma2)/2; epsilon=sqrt(epsilon1*epsilon2); " ) if combine_nonbonded_forces: non_bonded_force = openmm.NonbondedForce() openmm_sys.addForce(non_bonded_force) for _ in openff_sys.topology.mdtop.atoms: non_bonded_force.addParticle(0.0, 1.0, 0.0) if vdw_method == "cutoff" and electrostatics_method == "pme": if openff_sys.box is not None: non_bonded_force.setNonbondedMethod( openmm.NonbondedForce.PME) non_bonded_force.setUseDispersionCorrection(True) non_bonded_force.setCutoffDistance(vdw_cutoff) non_bonded_force.setEwaldErrorTolerance(1.0e-4) else: raise UnsupportedCutoffMethodError elif vdw_method == "pme" and electrostatics_method == "pme": if openff_sys.box is not None: non_bonded_force.setNonbondedMethod( openmm.NonbondedForce.LJPME) non_bonded_force.setEwaldErrorTolerance(1.0e-4) else: raise UnsupportedCutoffMethodError else: raise UnimplementedCutoffMethodError( f"Combination of non-bonded cutoff methods {vdw_cutoff} (vdW) and " f"{electrostatics_method} (Electrostatics) not currently supported with " f"`combine_nonbonded_forces={combine_nonbonded_forces}") else: vdw_expression = vdw_handler.expression vdw_expression = vdw_expression.replace("**", "^") vdw_force = openmm.CustomNonbondedForce(vdw_expression + "; " + mixing_rule_expression) openmm_sys.addForce(vdw_force) vdw_force.addPerParticleParameter("sigma") vdw_force.addPerParticleParameter("epsilon") # TODO: Add virtual particles for _ in openff_sys.topology.mdtop.atoms: vdw_force.addParticle([1.0, 0.0]) if vdw_method == "cutoff": if openff_sys.box is None: vdw_force.setNonbondedMethod( openmm.NonbondedForce.CutoffNonPeriodic) else: vdw_force.setNonbondedMethod( openmm.NonbondedForce.CutoffPeriodic) vdw_force.setUseLongRangeCorrection(True) vdw_force.setCutoffDistance(vdw_cutoff) if getattr(vdw_handler, "switch_width", None) is not None: if vdw_handler.switch_width == 0.0: vdw_force.setUseSwitchingFunction(False) else: switching_distance = (vdw_handler.cutoff - vdw_handler.switch_width) if switching_distance.m < 0: raise UnsupportedCutoffMethodError( "Found a 'switch_width' greater than the cutoff distance. It's not clear " "what this means and it's probably invalid. Found " f"switch_width{vdw_handler.switch_width} and cutoff {vdw_handler.cutoff}" ) switching_distance = ( switching_distance.m_as(off_unit.angstrom) * unit.angstrom) vdw_force.setUseSwitchingFunction(True) vdw_force.setSwitchingDistance(switching_distance) elif vdw_method == "pme": if openff_sys.box is None: raise UnsupportedCutoffMethodError( "vdW method pme/ljpme is not valid for non-periodic systems." ) else: # TODO: Fully flesh out this implementation - cutoffs, other settings vdw_force.setNonbondedMethod(openmm.NonbondedForce.PME) electrostatics_force = openmm.NonbondedForce() openmm_sys.addForce(electrostatics_force) for _ in openff_sys.topology.mdtop.atoms: electrostatics_force.addParticle(0.0, 1.0, 0.0) if electrostatics_method == "reaction-field": if openff_sys.box is None: # TODO: Should this state be prevented from happening? raise UnsupportedCutoffMethodError( f"Electrostatics method {electrostatics_method} is not valid for a non-periodic interchange." ) else: raise UnimplementedCutoffMethodError( f"Electrostatics method {electrostatics_method} is not yet implemented." ) elif electrostatics_method == "pme": electrostatics_force.setNonbondedMethod( openmm.NonbondedForce.PME) electrostatics_force.setEwaldErrorTolerance(1.0e-4) electrostatics_force.setUseDispersionCorrection(True) elif electrostatics_method == "cutoff": raise UnsupportedCutoffMethodError( "OpenMM does not clearly support cut-off electrostatics with no reaction-field attenuation." ) else: raise UnsupportedCutoffMethodError( f"Electrostatics method {electrostatics_method} not supported" ) partial_charges = electrostatics_handler.charges for top_key, pot_key in vdw_handler.slot_map.items(): atom_idx = top_key.atom_indices[0] partial_charge = partial_charges[top_key] # partial_charge = partial_charge.m_as(off_unit.elementary_charge) vdw_potential = vdw_handler.potentials[pot_key] # these are floats, implicitly angstrom and kcal/mol sigma, epsilon = _lj_params_from_potential(vdw_potential) sigma = sigma.m_as(off_unit.nanometer) epsilon = epsilon.m_as(off_unit.kilojoule / off_unit.mol) if combine_nonbonded_forces: non_bonded_force.setParticleParameters( atom_idx, partial_charge.m_as(off_unit.e), sigma, epsilon, ) else: vdw_force.setParticleParameters(atom_idx, [sigma, epsilon]) electrostatics_force.setParticleParameters( atom_idx, partial_charge.m_as(off_unit.e), 0.0, 0.0) elif "Buckingham-6" in openff_sys.handlers: buck_handler = openff_sys.handlers["Buckingham-6"] non_bonded_force = openmm.CustomNonbondedForce( "A * exp(-B * r) - C * r ^ -6; A = sqrt(A1 * A2); B = 2 / (1 / B1 + 1 / B2); C = sqrt(C1 * C2)" ) non_bonded_force.addPerParticleParameter("A") non_bonded_force.addPerParticleParameter("B") non_bonded_force.addPerParticleParameter("C") openmm_sys.addForce(non_bonded_force) for _ in openff_sys.topology.mdtop.atoms: non_bonded_force.addParticle([0.0, 0.0, 0.0]) if openff_sys.box is None: non_bonded_force.setNonbondedMethod(openmm.NonbondedForce.NoCutoff) else: non_bonded_force.setNonbondedMethod( openmm.NonbondedForce.CutoffPeriodic) non_bonded_force.setCutoffDistance(buck_handler.cutoff * unit.angstrom) for top_key, pot_key in buck_handler.slot_map.items(): atom_idx = top_key.atom_indices[0] # TODO: Add electrostatics params = buck_handler.potentials[pot_key].parameters a = pint_to_simtk(params["A"]) b = pint_to_simtk(params["B"]) c = pint_to_simtk(params["C"]) non_bonded_force.setParticleParameters(atom_idx, [a, b, c]) return if not combine_nonbonded_forces: # Attempting to match the value used internally by OpenMM; The source of this value is likely # https://github.com/openmm/openmm/issues/1149#issuecomment-250299854 # 1 / * (4pi * eps0) * elementary_charge ** 2 / nanometer ** 2 coul_const = 138.935456 # kJ/nm vdw_14_force = openmm.CustomBondForce( "4*epsilon*((sigma/r)^12-(sigma/r)^6)") vdw_14_force.addPerBondParameter("sigma") vdw_14_force.addPerBondParameter("epsilon") vdw_14_force.setUsesPeriodicBoundaryConditions(True) coul_14_force = openmm.CustomBondForce(f"{coul_const}*qq/r") coul_14_force.addPerBondParameter("qq") coul_14_force.setUsesPeriodicBoundaryConditions(True) openmm_sys.addForce(vdw_14_force) openmm_sys.addForce(coul_14_force) # Need to create 1-4 exceptions, just to have a baseline for splitting out/modifying # It might be simpler to iterate over 1-4 pairs directly bonds = [(b.atom1.index, b.atom2.index) for b in openff_sys.topology.mdtop.bonds] if combine_nonbonded_forces: non_bonded_force.createExceptionsFromBonds( bonds=bonds, coulomb14Scale=electrostatics_handler.scale_14, lj14Scale=vdw_handler.scale_14, ) else: electrostatics_force.createExceptionsFromBonds( bonds=bonds, coulomb14Scale=electrostatics_handler.scale_14, lj14Scale=vdw_handler.scale_14, ) for i in range(electrostatics_force.getNumExceptions()): (p1, p2, q, sig, eps) = electrostatics_force.getExceptionParameters(i) # If the interactions are both zero, assume this is a 1-2 or 1-3 interaction if q._value == 0 and eps._value == 0: pass else: # Assume this is a 1-4 interaction # Look up the vdW parameters for each particle sig1, eps1 = vdw_force.getParticleParameters(p1) sig2, eps2 = vdw_force.getParticleParameters(p2) q1, _, _ = electrostatics_force.getParticleParameters(p1) q2, _, _ = electrostatics_force.getParticleParameters(p2) # manually compute and set the 1-4 interactions sig_14 = (sig1 + sig2) * 0.5 eps_14 = (eps1 * eps2)**0.5 * vdw_handler.scale_14 qq = q1 * q2 * electrostatics_handler.scale_14 vdw_14_force.addBond(p1, p2, [sig_14, eps_14]) coul_14_force.addBond(p1, p2, [qq]) vdw_force.addExclusion(p1, p2) # electrostatics_force.addExclusion(p1, p2) electrostatics_force.setExceptionParameters( i, p1, p2, 0.0, 0.0, 0.0)
# Add a Monte Carlo barostat to the system for pressure control system.addForce( mm.MonteCarloBarostat(1 * unit.atmospheres, 300 * unit.kelvin, 25)) # Use the CPU platform platform = mm.Platform.getPlatformByName('CPU') ### If you want to add any forces to your System or modify any ### of the existing forces, you should do it here - after the ### System has been created, but before the Simulation is created. ### these lines are for adding a force between atoms 1 and 273, the CA atoms ### of the first and last residues of Trp-cage, to unfold the protein custom_bond = mm.CustomBondForce("0.5*k*(r-r0)^2") #import IPython #IPython.embed() custom_bond.addGlobalParameter("k", 1000) custom_bond.addGlobalParameter("r0", 5.0) custom_bond.addBond(1, 273) system.addForce(custom_bond) # Create a Simulation object by putting together the objects above simulation = app.Simulation(pdb.topology, system, integrator, platform) # Set positions in the Simulation object simulation.context.setPositions(pdb.positions) # Minimize the energy of the system (intentionally doing a rough minimization) print('Minimizing...')
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
def WCADimer(N=natoms, density=density, mm=None, mass=mass, epsilon=epsilon, sigma=sigma, h=h, r0=r0, w=w): """ Create a bistable bonded pair of particles (indices 0 and 1) optionally surrounded by a Weeks-Chandler-Andersen fluid. The bistable potential has form U(r) = h*(1-((r-r0-w)/w)^2)^2 where r0 is the compact state separation, r0+2w is the extended state separation, and h is the barrier height. The WCA potential has form U(r) = 4 epsilon [ (sigma/r)^12 - (sigma/r)^6 ] + epsilon (r < r*) = 0 (r >= r*) where r* = 2^(1/6) sigma. OPTIONAL ARGUMENTS N (int) - total number of atoms (default: 2) density (float) - number density of particles (default: 0.96 / sigma**3) mass (simtk.unit.Quantity of mass) - particle mass (default: 39.948 amu) sigma (simtk.unit.Quantity of length) - Lennard-Jones sigma parameter (default: 0.3405 nm) epsilon (simtk.unit.Quantity of energy) - Lennard-Jones well depth (default: (119.8 Kelvin)*kB) h (simtk.unit.Quantity of energy) - bistable potential barrier height (default: ???) r0 (simtk.unit.Quantity of length) - bistable potential compact state separation (default: ???) w (simtk.unit.Quantity of length) - bistable potential extended state separation is r0+2*w (default: ???) """ # Choose OpenMM package. if mm is None: mm = openmm # Compute cutoff for WCA fluid. r_WCA = 2.**(1. / 6.) * sigma # cutoff at minimum of potential # Create system system = mm.System() # Compute total system volume. volume = N / density # Make system cubic in dimension. length = volume**(1.0 / 3.0) a = units.Quantity(numpy.array([1.0, 0.0, 0.0], numpy.float32), units.nanometer) * length / units.nanometer b = units.Quantity(numpy.array([0.0, 1.0, 0.0], numpy.float32), units.nanometer) * length / units.nanometer c = units.Quantity(numpy.array([0.0, 0.0, 1.0], numpy.float32), units.nanometer) * length / units.nanometer print("box edge length = %s" % str(length)) system.setDefaultPeriodicBoxVectors(a, b, c) # Add particles to system. for n in range(N): system.addParticle(mass) # WCA: Lennard-Jones truncated at minim and shifted so potential is zero at cutoff. energy_expression = '4.0*epsilon*((sigma/r)^12 - (sigma/r)^6) + epsilon' # Create force. force = mm.CustomNonbondedForce(energy_expression) # Set epsilon and sigma global parameters. force.addGlobalParameter('epsilon', epsilon) force.addGlobalParameter('sigma', sigma) # Add particles for n in range(N): force.addParticle([]) # Add exclusion between bonded particles. force.addExclusion(0, 1) # Set periodic boundary conditions with cutoff. if (N > 2): force.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic) else: force.setNonbondedMethod(mm.CustomNonbondedForce.CutoffNonPeriodic) print("setting cutoff distance to %s" % str(r_WCA)) force.setCutoffDistance(r_WCA) # Add nonbonded force term to the system. system.addForce(force) # Add dimer potential to first two particles. dimer_force = openmm.CustomBondForce('h*(1-((r-r0-w)/w)^2)^2;') dimer_force.addGlobalParameter('h', h) # barrier height dimer_force.addGlobalParameter('r0', r0) # compact state separation dimer_force.addGlobalParameter('w', w) # second minimum is at r0 + 2*w dimer_force.addBond(0, 1, []) system.addForce(dimer_force) # Create initial coordinates using random positions. coordinates = units.Quantity(numpy.random.rand(N, 3), units.nanometer) * (length / units.nanometer) # Reposition dimer particles at compact minimum. coordinates[0, :] *= 0.0 coordinates[1, :] *= 0.0 coordinates[1, 0] = r0 # Return system and coordinates. return [system, coordinates]
def createPerturbedSystem(self, alchemical_state, mm=None, verbose=False): """ Create a perturbed copy of the system given the specified alchemical state. ARGUMENTS alchemical_state (AlchemicalState) - the alchemical state to create from the reference system TODO * Start from a deep copy of the system, rather than building copy through Python interface. * isinstance(mm.NonbondedForce) and related expressions won't work if reference system was created with a different OpenMM implemnetation. EXAMPLES Create alchemical intermediates for 'denihilating' one water in a water box. >>> # Create a reference system. >>> import testsystems >>> [reference_system, coordinates] = testsystems.WaterBox() >>> # Create a factory to produce alchemical intermediates. >>> factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=[0, 1, 2]) >>> # Create an alchemically-perturbed state corresponding to fully-interacting. >>> alchemical_state = AlchemicalState(0.00, 1.00, 1.00, 1.) >>> # Create the perturbed system. >>> alchemical_system = factory.createPerturbedSystem(alchemical_state) >>> # Compare energies. >>> import simtk.openmm as openmm >>> import simtk.unit as units >>> timestep = 1.0 * units.femtosecond >>> reference_integrator = openmm.VerletIntegrator(timestep) >>> reference_context = openmm.Context(reference_system, reference_integrator) >>> reference_state = reference_context.getState(getEnergy=True) >>> reference_potential = reference_state.getPotentialEnergy() >>> alchemical_integrator = openmm.VerletIntegrator(timestep) >>> alchemical_context = openmm.Context(alchemical_system, alchemical_integrator) >>> alchemical_state = alchemical_context.getState(getEnergy=True) >>> alchemical_potential = alchemical_state.getPotentialEnergy() >>> delta = alchemical_potential - reference_potential >>> print delta 0.0 kJ/mol Create alchemical intermediates for 'denihilating' p-xylene in T4 lysozyme L99A in GBSA. >>> # Create a reference system. >>> import testsystems >>> [reference_system, coordinates] = testsystems.LysozymeImplicit() >>> # Compute reference potential. >>> timestep = 1.0 * units.femtosecond >>> reference_integrator = openmm.VerletIntegrator(timestep) >>> reference_context = openmm.Context(reference_system, reference_integrator) >>> reference_context.setPositions(coordinates) >>> reference_state = reference_context.getState(getEnergy=True) >>> reference_potential = reference_state.getPotentialEnergy() >>> # Create a factory to produce alchemical intermediates. >>> receptor_atoms = range(0,2603) # T4 lysozyme L99A >>> ligand_atoms = range(2603,2621) # p-xylene >>> factory = AbsoluteAlchemicalFactory(reference_system, ligand_atoms=ligand_atoms) >>> # Create an alchemically-perturbed state corresponding to fully-interacting. >>> alchemical_state = AlchemicalState(0.00, 1.00, 1.00, 1.) >>> # Create the perturbed systems using this protocol. >>> alchemical_system = factory.createPerturbedSystem(alchemical_state) >>> # Compare energies. >>> alchemical_integrator = openmm.VerletIntegrator(timestep) >>> alchemical_context = openmm.Context(alchemical_system, alchemical_integrator) >>> alchemical_context.setPositions(coordinates) >>> alchemical_state = alchemical_context.getState(getEnergy=True) >>> alchemical_potential = alchemical_state.getPotentialEnergy() >>> delta = alchemical_potential - reference_potential >>> print delta 0.0 kJ/mol NOTES If lambda = 1.0 is specified for some force terms, they will not be replaced with modified forms. """ # Record timing statistics. initial_time = time.time() if verbose: print "Creating alchemically modified intermediate..." reference_system = self.reference_system # Create new deep copy reference system to modify. system = openmm.System() # Set periodic box vectors. [a, b, c] = reference_system.getDefaultPeriodicBoxVectors() system.setDefaultPeriodicBoxVectors(a, b, c) # Add atoms. for atom_index in range(reference_system.getNumParticles()): mass = reference_system.getParticleMass(atom_index) system.addParticle(mass) # Add constraints for constraint_index in range(reference_system.getNumConstraints()): [iatom, jatom, r0] = reference_system.getConstraintParameters(constraint_index) system.addConstraint(iatom, jatom, r0) # Modify forces as appropriate, copying other forces without modification. nforces = reference_system.getNumForces() for force_index in range(nforces): reference_force = reference_system.getForce(force_index) if isinstance(reference_force, openmm.PeriodicTorsionForce): # PeriodicTorsionForce force = openmm.PeriodicTorsionForce() for torsion_index in range(reference_force.getNumTorsions()): # Retrieve parmaeters. [ particle1, particle2, particle3, particle4, periodicity, phase, k ] = reference_force.getTorsionParameters(torsion_index) # Scale torsion barrier of alchemically-modified system. if set([particle1, particle2, particle3, particle4]).issubset(self.ligand_atomset): k *= alchemical_state.ligandTorsions force.addTorsion(particle1, particle2, particle3, particle4, periodicity, phase, k) system.addForce(force) elif isinstance(reference_force, openmm.NonbondedForce): # Copy NonbondedForce. force = copy.deepcopy(reference_force) system.addForce(force) # Modify electrostatics. if alchemical_state.ligandElectrostatics != 1.0: for particle_index in range(force.getNumParticles()): # Retrieve parameters. [charge, sigma, epsilon] = force.getParticleParameters(particle_index) # Alchemically modify charges. if particle_index in self.ligand_atomset: charge *= alchemical_state.ligandElectrostatics # Set modified particle parameters. force.setParticleParameters(particle_index, charge, sigma, epsilon) for exception_index in range(force.getNumExceptions()): # Retrieve parameters. [iatom, jatom, chargeprod, sigma, epsilon ] = force.getExceptionParameters(exception_index) # Alchemically modify chargeprod. if (iatom in self.ligand_atomset) and ( jatom in self.ligand_atomset): if alchemical_state.annihilateElectrostatics: chargeprod *= alchemical_state.ligandElectrostatics**2 # Set modified exception parameters. force.setExceptionParameters(exception_index, iatom, jatom, chargeprod, sigma, epsilon) # Modify Lennard-Jones if required. if alchemical_state.ligandLennardJones != 1.0: # Create softcore Lennard-Jones interactions by modifying NonbondedForce and adding CustomNonbondedForce. #self._alchemicallyModifyLennardJones(system, force, self.ligand_atoms, alchemical_state) self._alchemicallyModifyLennardJonesGroup( system, force, self.ligand_atoms, alchemical_state) elif isinstance(reference_force, openmm.GBSAOBCForce) and ( alchemical_state.ligandElectrostatics != 1.0): # Create a CustomNonbondedForce to implement softcore interactions. particle_lambdas = numpy.ones([system.getNumParticles()], numpy.float32) particle_lambdas[ self.ligand_atoms] = alchemical_state.ligandElectrostatics custom_force = AbsoluteAlchemicalFactory._createCustomSoftcoreGBOBC( reference_force, particle_lambdas) system.addForce(custom_force) elif isinstance(reference_force, openmm.CustomExternalForce): force = openmm.CustomExternalForce( reference_force.getEnergyFunction()) for parameter_index in range( reference_force.getNumGlobalParameters()): name = reference_force.getGlobalParameterName( parameter_index) default_value = reference_force.getGlobalParameterDefaultValue( parameter_index) force.addGlobalParameter(name, default_value) for parameter_index in range( reference_force.getNumPerParticleParameters()): name = reference_force.getPerParticleParameterName( parameter_index) force.addPerParticleParameter(name) for index in range(reference_force.getNumParticles()): [particle_index, parameters] = reference_force.getParticleParameters(index) force.addParticle(particle_index, parameters) system.addForce(force) elif isinstance(reference_force, openmm.CustomBondForce): force = openmm.CustomBondForce( reference_force.getEnergyFunction()) for parameter_index in range( reference_force.getNumGlobalParameters()): name = reference_force.getGlobalParameterName( parameter_index) default_value = reference_force.getGlobalParameterDefaultValue( parameter_index) force.addGlobalParameter(name, default_value) for parameter_index in range( reference_force.getNumPerBondParameters()): name = reference_force.getPerBondParameterName( parameter_index) force.addPerBondParameter(name) for index in range(reference_force.getNumBonds()): [particle1, particle2, parameters] = reference_force.getBondParameters(index) force.addBond(particle1, particle2, parameters) system.addForce(force) else: # Copy force without modification. force = copy.deepcopy(reference_force) system.addForce(force) # Record timing statistics. final_time = time.time() elapsed_time = final_time - initial_time if verbose: print "Elapsed time %.3f s." % (elapsed_time) return system
# 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 thermodynamic_states = list() # Umbrella off state parameters = { 'torsion_K' : 0.0, 'torsion_theta0' : 0.0, # umbrella parameters 'distance_K' : 0.0, 'distance_r0' : 0.0, # umbrella parameters } thermodynamic_states.append( ThermodynamicState(system=system, temperature=temperature, pressure=pressure, parameters=parameters) )
def setup_fah_run(destination_path, protein_pdb_filename, oemol=None, cache=None, restrain_rmsd=False): """ Prepare simulation Parameters ---------- destination_path : str The path to the RUN to be created protein_pdb_filename : str Path to protein PDB file oemol : openeye.oechem.OEMol, optional, default=None The molecule to parameterize, with SDData attached If None, don't include the small molecule restrain_rmsd : bool, optional, default=False If True, restrain RMSD during first equilibration phase """ # Parameters from simtk import unit, openmm protein_forcefield = 'amber14/protein.ff14SB.xml' solvent_forcefield = 'amber14/tip3p.xml' small_molecule_forcefield = 'openff-1.2.0' water_model = 'tip3p' solvent_padding = 10.0 * unit.angstrom ionic_strength = 70 * unit.millimolar # assay buffer: 20 mM HEPES pH 7.3, 1 mM TCEP, 50 mM NaCl, 0.01% Tween-20, 10% glycerol pressure = 1.0 * unit.atmospheres collision_rate = 1.0 / unit.picoseconds temperature = 300.0 * unit.kelvin timestep = 4.0 * unit.femtoseconds iterations = 1000 # 1 ns equilibration nsteps_per_iteration = 250 # Prepare phases import os system_xml_filename = os.path.join(destination_path, 'system.xml.bz2') integrator_xml_filename = os.path.join(destination_path, 'integrator.xml.bz2') state_xml_filename = os.path.join(destination_path, 'state.xml.bz2') # Check if we can skip setup openmm_files_exist = os.path.exists( system_xml_filename) and os.path.exists( state_xml_filename) and os.path.exists(integrator_xml_filename) if openmm_files_exist: return # Create barostat barostat = openmm.MonteCarloBarostat(pressure, temperature) # Create RUN directory if it does not yet exist os.makedirs(destination_path, exist_ok=True) # Load any molecule(s) molecule = None if oemol is not None: from openforcefield.topology import Molecule molecule = Molecule.from_openeye(oemol, allow_undefined_stereo=True) molecule.name = 'MOL' # Ensure residue is MOL print([res for res in molecule.to_topology().to_openmm().residues()]) # Create SystemGenerator import os from simtk.openmm import app forcefield_kwargs = { 'removeCMMotion': False, 'hydrogenMass': 3.0 * unit.amu, 'constraints': app.HBonds, 'rigidWater': True } periodic_kwargs = { 'nonbondedMethod': app.PME, 'ewaldErrorTolerance': 2.5e-04 } forcefields = [protein_forcefield, solvent_forcefield] from openmmforcefields.generators import SystemGenerator openmm_system_generator = SystemGenerator( forcefields=forcefields, molecules=molecule, small_molecule_forcefield=small_molecule_forcefield, cache=cache, barostat=barostat, forcefield_kwargs=forcefield_kwargs, periodic_forcefield_kwargs=periodic_kwargs) # Read protein print(f'Reading protein from {protein_pdb_filename}...') pdbfile = app.PDBFile(protein_pdb_filename) modeller = app.Modeller(pdbfile.topology, pdbfile.positions) if oemol is not None: # Add small molecule to the system modeller.add(molecule.to_topology().to_openmm(), molecule.conformers[0]) # DEBUG : Check residue name with open(os.path.join(destination_path, 'initial-complex.pdb'), 'wt') as outfile: app.PDBFile.writeFile(modeller.topology, modeller.positions, outfile) # Add solvent print('Adding solvent...') kwargs = {'padding': solvent_padding} modeller.addSolvent(openmm_system_generator.forcefield, model='tip3p', ionicStrength=ionic_strength, **kwargs) # Create an OpenMM system print('Creating OpenMM system...') system = openmm_system_generator.create_system(modeller.topology) # Add a virtual bond between protein and ligand to make sure they are not imaged separately if oemol is not None: import mdtraj as md mdtop = md.Topology.from_openmm( modeller.topology) # excludes solvent and ions for res in mdtop.residues: print(res) protein_atom_indices = mdtop.select( '(protein and name CA)') # protein CA atoms ligand_atom_indices = mdtop.select( '((resname MOL) and (mass > 1))') # ligand heavy atoms protein_atom_index = int(protein_atom_indices[0]) ligand_atom_index = int(ligand_atom_indices[0]) force = openmm.CustomBondForce('0') force.addBond(protein_atom_index, ligand_atom_index, []) system.addForce(force) # Add RMSD restraints if requested if restrain_rmsd: print('Adding RMSD restraint...') kB = unit.AVOGADRO_CONSTANT_NA * unit.BOLTZMANN_CONSTANT_kB kT = kB * temperature import mdtraj as md mdtop = md.Topology.from_openmm( pdbfile.topology) # excludes solvent and ions #heavy_atom_indices = mdtop.select('mass > 1') # heavy solute atoms rmsd_atom_indices = mdtop.select( '(protein and (name CA)) or ((resname MOL) and (mass > 1))' ) # CA atoms and ligand heavy atoms rmsd_atom_indices = [int(index) for index in rmsd_atom_indices] custom_cv_force = openmm.CustomCVForce('(K_RMSD/2)*RMSD^2') custom_cv_force.addGlobalParameter('K_RMSD', kT / unit.angstrom**2) rmsd_force = openmm.RMSDForce(modeller.positions, rmsd_atom_indices) custom_cv_force.addCollectiveVariable('RMSD', rmsd_force) force_index = system.addForce(custom_cv_force) # Create OpenM Context platform = openmm.Platform.getPlatformByName('OpenCL') platform.setPropertyDefaultValue('Precision', 'mixed') from openmmtools import integrators integrator = integrators.LangevinIntegrator(temperature, collision_rate, timestep) context = openmm.Context(system, integrator, platform) context.setPositions(modeller.positions) # Report initial potential energy state = context.getState(getEnergy=True) print( f'Initial potential energy is {state.getPotentialEnergy()/unit.kilocalories_per_mole:.3f} kcal/mol' ) # Store snapshots in MDTraj trajectory to examine RMSD import mdtraj as md import numpy as np mdtop = md.Topology.from_openmm(pdbfile.topology) atom_indices = mdtop.select('all') # all solute atoms protein_atom_indices = mdtop.select( 'protein and (mass > 1)') # heavy solute atoms if oemol is not None: ligand_atom_indices = mdtop.select( '(resname MOL) and (mass > 1)') # ligand heavy atoms trajectory = md.Trajectory( np.zeros([iterations + 1, len(atom_indices), 3], np.float32), mdtop) trajectory.xyz[0, :, :] = context.getState(getPositions=True).getPositions( asNumpy=True)[atom_indices] / unit.nanometers # Minimize print('Minimizing...') openmm.LocalEnergyMinimizer.minimize(context) # Equilibrate (with RMSD restraint if needed) import numpy as np from rich.progress import track import time initial_time = time.time() for iteration in track(range(iterations), 'Equilibrating...'): integrator.step(nsteps_per_iteration) trajectory.xyz[iteration + 1, :, :] = context.getState( getPositions=True).getPositions( asNumpy=True)[atom_indices] / unit.nanometers elapsed_time = (time.time() - initial_time) * unit.seconds ns_per_day = (context.getState().getTime() / elapsed_time) / (unit.nanoseconds / unit.day) print(f'Performance: {ns_per_day:8.3f} ns/day') if restrain_rmsd: # Disable RMSD restraint context.setParameter('K_RMSD', 0.0) print('Minimizing...') openmm.LocalEnergyMinimizer.minimize(context) for iteration in track(range(iterations), 'Equilibrating without RMSD restraint...'): integrator.step(nsteps_per_iteration) # Retrieve state state = context.getState(getPositions=True, getVelocities=True, getEnergy=True, getForces=True) system.setDefaultPeriodicBoxVectors(*state.getPeriodicBoxVectors()) modeller.topology.setPeriodicBoxVectors(state.getPeriodicBoxVectors()) print( f'Final potential energy is {state.getPotentialEnergy()/unit.kilocalories_per_mole:.3f} kcal/mol' ) # Equilibrate again if we restrained the RMSD if restrain_rmsd: print('Removing RMSD restraint from system...') system.removeForce(force_index) #if oemol is not None: # # Check final RMSD # print('checking RMSD...') # trajectory.superpose(trajectory, atom_indices=protein_atom_indices) # protein_rmsd = md.rmsd(trajectory, trajectory[-1], atom_indices=protein_atom_indices)[-1] * 10 # Angstroms # oechem.OESetSDData(oemol, 'equil_protein_rmsd', f'{protein_rmsd:.2f} A') # ligand_rmsd = md.rmsd(trajectory, trajectory[-1], atom_indices=ligand_atom_indices)[-1] * 10 # Angstroms # oechem.OESetSDData(oemol, 'equil_ligand_rmsd', f'{ligand_rmsd:.2f} A') # print('RMSD after equilibration: protein {protein_rmsd:8.2f} A | ligand {ligand_rmsd:8.3f} A') # Save as OpenMM print('Exporting for OpenMM FAH simulation...') import bz2 with bz2.open(integrator_xml_filename, 'wt') as f: f.write(openmm.XmlSerializer.serialize(integrator)) with bz2.open(state_xml_filename, 'wt') as f: f.write(openmm.XmlSerializer.serialize(state)) with bz2.open(system_xml_filename, 'wt') as f: f.write(openmm.XmlSerializer.serialize(system)) with bz2.open(os.path.join(destination_path, 'equilibrated-all.pdb.gz'), 'wt') as f: app.PDBFile.writeFile(modeller.topology, state.getPositions(), f) with open(os.path.join(destination_path, 'equilibrated-solute.pdb'), 'wt') as f: import mdtraj mdtraj_topology = mdtraj.Topology.from_openmm(modeller.topology) mdtraj_trajectory = mdtraj.Trajectory( [state.getPositions(asNumpy=True) / unit.nanometers], mdtraj_topology) selection = mdtraj_topology.select('not water') mdtraj_trajectory = mdtraj_trajectory.atom_slice(selection) app.PDBFile.writeFile(mdtraj_trajectory.topology.to_openmm(), mdtraj_trajectory.openmm_positions(0), f) if oemol is not None: # Write molecule as SDF, SMILES, and mol2 for extension in ['sdf', 'mol2', 'smi', 'csv']: filename = os.path.join(destination_path, f'molecule.{extension}') with oechem.oemolostream(filename) as ofs: oechem.OEWriteMolecule(ofs, oemol) # Clean up del context, integrator