def get_potential_energy(item, coordinates=None, platform_name='CUDA'): from simtk.openmm import LangevinIntegrator, Platform, Context from simtk import unit import numpy as np integrator = LangevinIntegrator(0.0 * unit.kelvin, 0.0 / unit.picoseconds, 2.0 * unit.femtoseconds) platform = Platform.getPlatformByName(platform_name) context = Context(item.system, integrator, platform) if coordinates is None: context.setPositions(item.coordinates) else: context.setPositions(coordinates) if item.box is not None: context.setPeriodicBoxVectors(item.box[0], item.box[1], item.box[2]) state = context.getState(getEnergy=True) potential_energy = state.getPotentialEnergy() return potential_energy
def energy_minimization(item, platform_name='CUDA', verbose=True): from simtk.openmm import LangevinIntegrator, Platform, Context, LocalEnergyMinimizer_minimize from simtk import unit # Integrator. integrator = LangevinIntegrator(0 * unit.kelvin, 1.0 / unit.picoseconds, 2.0 * unit.femtoseconds) # Platform. platform = Platform.getPlatformByName(platform_name) # Context. context = Context(item.system, integrator, platform) context.setPositions(item.coordinates) # Minimization. if verbose == True: energy = context.getState(getEnergy=True).getPotentialEnergy() print('Potential energy before minimization: {}'.format(energy)) LocalEnergyMinimizer_minimize(context) if verbose == True: energy = context.getState(getEnergy=True).getPotentialEnergy() print('Potential energy after minimization: {}'.format(energy)) item.coordinates = context.getState(getPositions=True).getPositions( asNumpy=True) pass
def _initialize(self): system = self._explorer.context.getSystem() platform = self._explorer.context.getPlatform() properties = {} if platform.getName()=='CUDA': properties['CudaPrecision'] = 'mixed' self._integrator = GradientDescentMinimizationIntegrator(initial_step_size=self._initial_step_size) self._context = Context(system, self._integrator, platform, properties) self._initialized = True
def __init__(self, topology=None, system=None, pbc=False, platform='CUDA'): from .md import MD from .quench import Quench from .move import Move from .distance import Distance from .acceptance import Acceptance if topology is None: raise ValueError('topology is needed') if system is None: raise ValueError('system is needed') integrator = LangevinIntegrator(0 * u.kelvin, 1.0 / u.picoseconds, 2.0 * u.femtoseconds) #integrator.setConstraintTolerance(0.00001) if platform == 'CUDA': platform = Platform.getPlatformByName('CUDA') properties = {'CudaPrecision': 'mixed'} elif platform == 'CPU': platform = Platform.getPlatformByName('CPU') properties = {} self.topology = topology self.context = Context(system, integrator, platform, properties) self.n_atoms = msm.get(self.context, target='system', n_atoms=True) self.n_dof = 0 for i in range(system.getNumParticles()): if system.getParticleMass(i) > 0 * u.dalton: self.n_dof += 3 for i in range(system.getNumConstraints()): p1, p2, distance = system.getConstraintParameters(i) if system.getParticleMass( p1) > 0 * u.dalton or system.getParticleMass( p2) > 0 * u.dalton: self.n_dof -= 1 if any( type(system.getForce(i)) == CMMotionRemover for i in range(system.getNumForces())): self.n_dof -= 3 self.pbc = pbc if self.pbc: raise NotImplementedError self.md = MD(self) self.quench = Quench(self) self.move = Move(self) self.distance = Distance(self) self.acceptance = Acceptance(self)
def run(self): """Run the process: set positions and compute energies and forces. Positions and box vectors are received from the task_queue in units of nanometers. Energies and forces are pushed to the result_queue in units of kJ/mole and kJ/mole/nm, respectively. """ from simtk import unit from simtk.openmm import Platform, Context # create the context # it is crucial to do that in the run function and not in the constructor # for some reason, the CPU platform hangs if the context is created in the constructor # see also https://github.com/openmm/openmm/issues/2602 openmm_platform = Platform.getPlatformByName(self._openmm_platform_name) self._openmm_context = Context( self._openmm_system, self._openmm_integrator, openmm_platform, self._openmm_platform_properties ) self._openmm_context.reinitialize(preserveState=True) # get tasks from the task queue for task in iter(self._task_queue.get, None): (index, positions, box_vectors, evaluate_energy, evaluate_force, evaluate_positions, evaluate_path_probability_ratio, err_handling, n_simulation_steps) = task try: # initialize state self._openmm_context.setPositions(positions) if box_vectors is not None: self._openmm_context.setPeriodicBoxVectors(box_vectors) log_path_probability_ratio = self._openmm_integrator.step(n_simulation_steps) # compute energy and forces state = self._openmm_context.getState( getEnergy=evaluate_energy, getForces=evaluate_force, getPositions=evaluate_positions ) energy = state.getPotentialEnergy().value_in_unit(unit.kilojoule_per_mole) if evaluate_energy else None forces = ( state.getForces(asNumpy=True).value_in_unit(unit.kilojoule_per_mole / unit.nanometer) if evaluate_force else None ) new_positions = state.getPositions().value_in_unit(unit.nanometers) if evaluate_positions else None except Exception as e: if err_handling == "warning": warnings.warn("Suppressed exception: {}".format(e)) elif err_handling == "exception": raise e # push energies and forces to the results queue self._result_queue.put( [index, energy, forces, new_positions, log_path_probability_ratio] )
def _initialize(self): system = self._explorer.context.getSystem() platform = self._explorer.context.getPlatform() properties = {} if platform.getName() == 'CUDA': properties['CudaPrecision'] = 'mixed' self._integrator = LangevinIntegrator(self._temperature, self._collision_rate, self._timestep) self._context = Context(system, self._integrator, platform, properties) self._initialized = True
def __init__(self, system, integrator=None): # if strings are passed in, assume that they are paths to # xml files on disk if isinstance(system, basestring): with open(system) as f: system = XmlSerializer.deserialize(f.read()) if isinstance(integrator, basestring): with open(integrator) as f: integrator = XmlSerializer.deserialize(f.read()) if integrator is None: # this integrator isn't really necessary, but it has to be something # for the openmm API to let us serialize the state integrator = VerletIntegrator(2 * femtoseconds) self.context = Context(system, integrator, Platform.getPlatformByName('Reference'))
def _initialize(self): system = self._explorer.context.getSystem() platform = self._explorer.context.getPlatform() properties = {} if platform.getName() == 'CUDA': properties['CudaPrecision'] = 'mixed' self._integrator = FIREMinimizationIntegrator( timestep=self._timestep, tolerance=self._tolerance, alpha=self._alpha, dt_max=self._dt_max, f_inc=self._f_inc, f_dec=self._f_dec, f_alpha=self._f_alpha, N_min=self._N_min) self._context = Context(system, self._integrator, platform, properties) self._initialized = True
def __init__(self, n_workers, system, integrator, platform_name, platform_properties={}): """Set up workers and queues.""" from simtk.openmm import Platform, Context assert n_workers == 1 openmm_platform = Platform.getPlatformByName(platform_name) self._openmm_context = Context(system, integrator, openmm_platform, platform_properties)
def addHydrogens(self, forcefield=None, pH=None, variants=None, platform=None): """Add missing hydrogens to the model. This function automatically changes compatible residues into their constant-pH variant if no variant is specified.: Aspartic acid: AS4: Form with a 2 hydrogens on each one of the delta oxygens (syn,anti) It has 5 titration states. Alternative: AS2: Has 2 hydrogens (syn, anti) on one of the delta oxygens It has 3 titration states. Cysteine: CYS: Neutral form with a hydrogen on the sulfur CYX: No hydrogen on the sulfur (either negatively charged, or part of a disulfide bond) Glutamic acid: GL4: Form with a 2 hydrogens on each one of the epsilon oxygens (syn,anti) It has 5 titration states. Histidine: HIP: Positively charged form with hydrogens on both ND1 and NE2 It has 3 titration states. The variant to use for each residue is determined by the following rules: 1. Any Cysteine that participates in a disulfide bond uses the CYX variant regardless of pH. 2. Other residues are all set to maximally protonated state, which can be updated using a proton drive You can override these rules by explicitly specifying a variant for any residue. To do that, provide a list for the 'variants' parameter, and set the corresponding element to the name of the variant to use. A special case is when the model already contains a hydrogen that should not be present in the desired variant. If you explicitly specify a variant using the 'variants' parameter, the residue will be modified to match the desired variant, removing hydrogens if necessary. On the other hand, for residues whose variant is selected automatically, this function will only add hydrogens. It will never remove ones that are already present in the model. Definitions for standard amino acids and nucleotides are built in. You can call loadHydrogenDefinitions() to load additional definitions for other residue types. Parameters ---------- forcefield : ForceField=None the ForceField to use for determining the positions of hydrogens. If this is None, positions will be picked which are generally reasonable but not optimized for any particular ForceField. pH : None, Kept for compatibility reasons. Has no effect. variants : list=None an optional list of variants to use. If this is specified, its length must equal the number of residues in the model. variants[i] is the name of the variant to use for residue i (indexed starting at 0). If an element is None, the standard rules will be followed to select a variant for that residue. platform : Platform=None the Platform to use when computing the hydrogen atom positions. If this is None, the default Platform will be used. Returns ------- list a list of what variant was actually selected for each residue, in the same format as the variants parameter Notes ----- This function does not use a pH specification. The argument is kept for compatibility reasons. """ # Check the list of variants. if pH is not None: print("Ignored pH argument provided for constant-pH residues.") residues = list(self.topology.residues()) if variants is not None: if len(variants) != len(residues): raise ValueError( "The length of the variants list must equal the number of residues" ) else: variants = [None] * len(residues) actualVariants = [None] * len(residues) # Load the residue specifications. if not Modeller._hasLoadedStandardHydrogens: Modeller.loadHydrogenDefinitions( os.path.join(os.path.dirname(__file__), "data", "hydrogens-amber10-constph.xml")) # Make a list of atoms bonded to each atom. bonded = {} for atom in self.topology.atoms(): bonded[atom] = [] for atom1, atom2 in self.topology.bonds(): bonded[atom1].append(atom2) bonded[atom2].append(atom1) # Define a function that decides whether a set of atoms form a hydrogen bond, using fairly tolerant criteria. def isHbond(d, h, a): if norm(d - a) > 0.35 * nanometer: return False deltaDH = h - d deltaHA = a - h deltaDH /= norm(deltaDH) deltaHA /= norm(deltaHA) return acos(dot(deltaDH, deltaHA)) < 50 * degree # Loop over residues. newTopology = Topology() newTopology.setPeriodicBoxVectors( self.topology.getPeriodicBoxVectors()) newAtoms = {} newPositions = [] * nanometer newIndices = [] acceptors = [ atom for atom in self.topology.atoms() if atom.element in (elem.oxygen, elem.nitrogen) ] for chain in self.topology.chains(): newChain = newTopology.addChain(chain.id) for residue in chain.residues(): newResidue = newTopology.addResidue(residue.name, newChain, residue.id) isNTerminal = residue == chain._residues[0] isCTerminal = residue == chain._residues[-1] if residue.name in Modeller._residueHydrogens: # Add hydrogens. First select which variant to use. spec = Modeller._residueHydrogens[residue.name] variant = variants[residue.index] if variant is None: if residue.name == "CYS": # If this is part of a disulfide, use CYX. sulfur = [ atom for atom in residue.atoms() if atom.element == elem.sulfur ] if len(sulfur) == 1 and any( (atom.residue != residue for atom in bonded[sulfur[0]])): variant = "CYX" if residue.name == "HIS": variant = "HIP" if residue.name == "GLU": variant = "GL4" if residue.name == "ASP": variant = "AS4" if variant is not None and variant not in spec.variants: raise ValueError("Illegal variant for %s residue: %s" % (residue.name, variant)) actualVariants[residue.index] = variant removeExtraHydrogens = variants[residue.index] is not None # Make a list of hydrogens that should be present in the residue. parents = [ atom for atom in residue.atoms() if atom.element != elem.hydrogen ] parentNames = [atom.name for atom in parents] hydrogens = [ h for h in spec.hydrogens if (variant is None) or (h.variants is None) or ( h.variants is not None and variant in h.variants) ] hydrogens = [ h for h in hydrogens if h.terminal is None or ( isNTerminal and h.terminal == "N") or ( isCTerminal and h.terminal == "C") ] hydrogens = [ h for h in hydrogens if h.parent in parentNames ] # Loop over atoms in the residue, adding them to the new topology along with required hydrogens. for parent in residue.atoms(): # Check whether this is a hydrogen that should be removed. if (removeExtraHydrogens and parent.element == elem.hydrogen and not any(parent.name == h.name for h in hydrogens)): continue # Add the atom. newAtom = newTopology.addAtom(parent.name, parent.element, newResidue) newAtoms[parent] = newAtom newPositions.append( deepcopy(self.positions[parent.index])) if parent in parents: # Match expected hydrogens with existing ones and find which ones need to be added. existing = [ atom for atom in bonded[parent] if atom.element == elem.hydrogen ] expected = [ h for h in hydrogens if h.parent == parent.name ] if len(existing) < len(expected): # Try to match up existing hydrogens to expected ones. matches = [] for e in existing: match = [ h for h in expected if h.name == e.name ] if len(match) > 0: matches.append(match[0]) expected.remove(match[0]) else: matches.append(None) # If any hydrogens couldn't be matched by name, just match them arbitrarily. for i in range(len(matches)): if matches[i] is None: matches[i] = expected[-1] expected.remove(expected[-1]) # Add the missing hydrogens. for h in expected: newH = newTopology.addAtom( h.name, elem.hydrogen, newResidue) newIndices.append(newH.index) delta = Vec3(0, 0, 0) * nanometer if len(bonded[parent]) > 0: for other in bonded[parent]: delta += ( self.positions[parent.index] - self.positions[other.index]) else: delta = (Vec3( random.random(), random.random(), random.random(), ) * nanometer) delta *= 0.1 * nanometer / norm(delta) delta += (0.05 * Vec3( random.random(), random.random(), random.random(), ) * nanometer) delta *= 0.1 * nanometer / norm(delta) newPositions.append( self.positions[parent.index] + delta) newTopology.addBond(newAtom, newH) else: # Just copy over the residue. for atom in residue.atoms(): newAtom = newTopology.addAtom(atom.name, atom.element, newResidue) newAtoms[atom] = newAtom newPositions.append( deepcopy(self.positions[atom.index])) for bond in self.topology.bonds(): if bond[0] in newAtoms and bond[1] in newAtoms: newTopology.addBond(newAtoms[bond[0]], newAtoms[bond[1]]) # The hydrogens were added at random positions. Now perform an energy minimization to fix them up. if forcefield is not None: # Use the ForceField the user specified. system = forcefield.createSystem(newTopology, rigidWater=False) atoms = list(newTopology.atoms()) for i in range(system.getNumParticles()): if atoms[i].element != elem.hydrogen: # This is a heavy atom, so make it immobile. system.setParticleMass(i, 0) else: # Create a System that restrains the distance of each hydrogen from its parent atom # and causes hydrogens to spread out evenly. system = System() nonbonded = CustomNonbondedForce("100/((r/0.1)^4+1)") bonds = HarmonicBondForce() angles = HarmonicAngleForce() system.addForce(nonbonded) system.addForce(bonds) system.addForce(angles) bondedTo = [] for atom in newTopology.atoms(): nonbonded.addParticle([]) if atom.element != elem.hydrogen: system.addParticle(0.0) else: system.addParticle(1.0) bondedTo.append([]) for atom1, atom2 in newTopology.bonds(): if atom1.element == elem.hydrogen or atom2.element == elem.hydrogen: bonds.addBond(atom1.index, atom2.index, 0.1, 100_000.0) bondedTo[atom1.index].append(atom2) bondedTo[atom2.index].append(atom1) for residue in newTopology.residues(): if residue.name == "HOH": # Add an angle term to make the water geometry correct. atoms = list(residue.atoms()) oindex = [ i for i in range(len(atoms)) if atoms[i].element == elem.oxygen ] if len(atoms) == 3 and len(oindex) == 1: hindex = list(set([0, 1, 2]) - set(oindex)) angles.addAngle( atoms[hindex[0]].index, atoms[oindex[0]].index, atoms[hindex[1]].index, 1.824, 836.8, ) else: # Add angle terms for any hydroxyls. for atom in residue.atoms(): index = atom.index if (atom.element == elem.oxygen and len(bondedTo[index]) == 2 and elem.hydrogen in (a.element for a in bondedTo[index])): angles.addAngle( bondedTo[index][0].index, index, bondedTo[index][1].index, 1.894, 460.24, ) if platform is None: context = Context(system, VerletIntegrator(0.0)) else: context = Context(system, VerletIntegrator(0.0), platform) context.setPositions(newPositions) LocalEnergyMinimizer.minimize(context, 1.0, 50) self.topology = newTopology self.positions = context.getState(getPositions=True).getPositions() del context return actualVariants