def __del__(self): PDBFile.writeFooter(self._topology, self._out) self._out.close()
def __del__(self): if self._topology is not None: PDBFile.writeFooter(self._topology, self._out) self._out.close()
def close(self): PDBFile.writeFooter(self.topology, self.outfile) self.outfile.flush()
def prep_system(box_width): # if model not in supported_models: # raise Exception("Specified water model '%s' is not in list of supported models: %s" % (model, str(supported_models))) # Load forcefield for solvent model and ions. # force_fields = ['tip3p.xml'] # if ionic_strength != 0.0*unit.molar: # force_fields.append('amber99sb.xml') # For the ions. ff = app.ForceField('tip3p.xml') # Create empty topology and coordinates. top = app.Topology() pos = unit.Quantity((), unit.angstroms) # Create new Modeller instance. m = app.Modeller(top, pos) boxSize = Vec3(box_width, box_width, box_width) * unit.nanometers # boxSize = unit.Quantity(numpy.ones([3]) * box_edge / box_edge.unit, box_edge.unit) m.addSolvent(ff, boxSize=boxSize, model='tip3p') system = ff.createSystem(m.getTopology(), nonbondedMethod=app.NoCutoff, constraints=None, rigidWater=False) positions = m.getPositions() positions = unit.Quantity(np.array(positions / positions.unit), positions.unit) # pdb_str = io.StringIO() fname = "debug.pdb" fhandle = open(fname, "w") PDBFile.writeHeader(m.getTopology(), fhandle) PDBFile.writeModel(m.getTopology(), positions, fhandle, 0) PDBFile.writeFooter(m.getTopology(), fhandle) return system, positions, np.eye(3) * box_width, fname assert 0 # , positiveIon=positive_ion, # negativeIon=negative_ion, ionicStrength=ionic_strength) # Get new topology and coordinates. newtop = m.getTopology() newpos = m.getPositions() # Convert positions to numpy. positions = unit.Quantity(numpy.array(newpos / newpos.unit), newpos.unit) # Create OpenMM System. system = ff.createSystem(newtop, nonbondedMethod=nonbondedMethod, nonbondedCutoff=cutoff, constraints=None, rigidWater=constrained, removeCMMotion=False) # Set switching function and dispersion correction. forces = { system.getForce(index).__class__.__name__: system.getForce(index) for index in range(system.getNumForces()) } forces['NonbondedForce'].setUseSwitchingFunction(False) if switch_width is not None: forces['NonbondedForce'].setUseSwitchingFunction(True) forces['NonbondedForce'].setSwitchingDistance(cutoff - switch_width) forces['NonbondedForce'].setUseDispersionCorrection(dispersion_correction) forces['NonbondedForce'].setEwaldErrorTolerance(ewaldErrorTolerance) n_atoms = system.getNumParticles() self.ndof = 3 * n_atoms - (constrained * n_atoms) self.topology = m.getTopology() self.system = system self.positions = positions
def run_simulation(mol_name, mol, smirnoff_params, pdb, inference): omm_forcefield = app.ForceField('amber99sb.xml', 'tip3p.xml') system = omm_forcefield.createSystem( pdb.topology, nonbondedMethod=app.NoCutoff, constraints=None, rigidWater=False) coords = [] for x,y,z in pdb.positions: coords.append([to_md_units(x),to_md_units(y),to_md_units(z)]) coords = np.array(coords) host_conf = coords host_potentials, (host_params, host_param_groups), host_masses = serialize.deserialize_system(system) smirnoff = ForceField("test_forcefields/smirnoff99Frosst.offxml") # CAREFUL THIS SHOULD BE THE SINGLE SET OF CONSISTENT FORCEFIELDS am1 = True if am1: guest_potentials, smirnoff_params, smirnoff_param_groups, guest_conf, guest_masses = forcefield.parameterize(mol, smirnoff, am1) else: guest_potentials, _, smirnoff_param_groups, guest_conf, guest_masses = forcefield.parameterize(mol, smirnoff, am1) # guest_conf = rescale_and_center(guest_conf, scale_factor=3.0) combined_potentials, combined_params, combined_param_groups, combined_conf, combined_masses = forcefield.combiner( host_potentials, guest_potentials, host_params, smirnoff_params, host_param_groups, smirnoff_param_groups, host_conf, guest_conf, host_masses, guest_masses) num_host_atoms = host_conf.shape[0] def filter_groups(param_groups, groups): roll = np.zeros_like(param_groups) for g in groups: roll = np.logical_or(roll, param_groups == g) return roll # host_dp_idxs = np.argwhere(filter_groups(host_param_groups, [7])).reshape(-1) # guest_dp_idxs = np.argwhere(filter_groups(smirnoff_param_groups, [7])).reshape(-1) # 1. host bond lengths # 7. host charges # 8. host vdw sigma # 9. host vdw epsilon # 17. charge combined_dp_idxs = np.argwhere(filter_groups(combined_param_groups, [17])).reshape(-1) if inference: combined_dp_idxs = [] outfile = open("frames/"+mol_name+".pdb", "w") combined_pdb = Chem.CombineMols(Chem.MolFromPDBFile(pdb.filepath, removeHs=False), mol) combined_pdb_str = StringIO(Chem.MolToPDBBlock(combined_pdb)) cpdb = app.PDBFile(combined_pdb_str) PDBFile.writeHeader(cpdb.topology, outfile) def write_fn(x, frame_idx): PDBFile.writeModel(cpdb.topology, x, outfile, frame_idx) dG, dG_grads, all_xis = minimize( mol_name, num_host_atoms, combined_potentials, combined_params, combined_conf, combined_masses, combined_dp_idxs, writer_fn=write_fn ) PDBFile.writeFooter(cpdb.topology, outfile) outfile.flush() # print("CDPI", combined_dp_idxs) # (ytz): we need to offset this by number of host params to compute the indices # of the original ligand parameters. if not inference: ligand_dp_idxs = combined_dp_idxs - len(host_params) else: ligand_dp_idxs = None return dG, dG_grads, ligand_dp_idxs
def integrate(self, topology_proposal, initial_positions, direction='insert', platform=None): """ Performs NCMC switching to either delete or insert atoms according to the provided `topology_proposal`. For `delete`, the system is first modified from fully interacting to alchemically modified, and then NCMC switching is used to eliminate atoms. For `insert`, the system begins with eliminated atoms in an alchemically noninteracting form and NCMC switching is used to turn atoms on, followed by making system real. The contribution of transforming the real system to/from an alchemical system is included. Parameters ---------- topology_proposal : TopologyProposal Contains old/new Topology and System objects and atom mappings. initial_positions : simtk.unit.Quantity with dimension [natoms, 3] with units of distance. Positions of the atoms at the beginning of the NCMC switching. direction : str, optional, default='insert' Direction of alchemical switching: 'insert' causes lambda to switch from 0 to 1 over nsteps steps of integration 'delete' causes lambda to switch from 1 to 0 over nsteps steps of integration platform : simtk.openmm.Platform, optional, default=None If not None, this platform is used for integration. Returns ------- final_positions : simtk.unit.Quantity of dimensions [nparticles,3] with units compatible with angstroms The final positions after `nsteps` steps of alchemical switching logP : float The log acceptance probability of the switch potential : simtk.unit.Quantity with units compatible with kilocalories_per_mole For `delete`, the potential energy of the final (alchemically eliminated) conformation. For `insert`, the potential energy of the initial (alchemically eliminated) conformation. """ if direction not in ['insert', 'delete']: raise Exception("'direction' must be one of ['insert', 'delete']; was '%s' instead" % direction) if (self.nsteps == 0): # Special case of instantaneous insertion/deletion. logP = 0.0 final_positions = copy.deepcopy(initial_positions) from perses.tests.utils import compute_potential if direction == 'delete': potential = self.beta * compute_potential(topology_proposal.old_system, initial_positions, platform=self.platform) elif direction == 'insert': potential = self.beta * compute_potential(topology_proposal.new_system, initial_positions, platform=self.platform) return [final_positions, logP, potential] # Create alchemical system. [unmodified_system, alchemical_system] = self.make_alchemical_system(topology_proposal, direction=direction) # DEBUG: Compute initial potential of unmodified system and alchemical system to make sure finite. from perses.tests.utils import compute_potential #print(compute_potential(unmodified_system, initial_positions, platform=self.platform)) #print(compute_potential(alchemical_system, initial_positions, platform=self.platform)) # Select subset of switching functions based on which alchemical parameters are present in the system. available_parameters = self._getAvailableParameters(alchemical_system) functions = { parameter_name : self.functions[parameter_name] for parameter_name in self.functions if (parameter_name in available_parameters) } # Create an NCMC velocity Verlet integrator. integrator = NCMCAlchemicalIntegrator(self.temperature, alchemical_system, functions, nsteps=self.nsteps, timestep=self.timestep, direction=direction) # Set the constraint tolerance if specified. if self.constraint_tolerance is not None: integrator.setConstraintTolerance(self.constraint_tolerance) # Create a context on the specified platform. if self.platform is not None: context = openmm.Context(alchemical_system, integrator, self.platform) else: context = openmm.Context(alchemical_system, integrator) context.setPositions(initial_positions) context.applyConstraints(integrator.getConstraintTolerance()) # Set velocities to temperature and apply velocity constraints. context.setVelocitiesToTemperature(self.temperature) context.applyVelocityConstraints(integrator.getConstraintTolerance()) # Set initial context parameters. if direction == 'insert': for parameter_name in available_parameters: context.setParameter(parameter_name, 0) elif direction == 'delete': for parameter_name in available_parameters: context.setParameter(parameter_name, 1) # Compute initial potential of alchemical state. initial_potential = self.beta * context.getState(getEnergy=True).getPotentialEnergy() if np.isnan(initial_potential): raise NaNException("Initial potential of 'insert' operation is NaN (unmodified potential was %.3f kT, alchemical potential was %.3f kT before changing lambda)" % (unmodified_potential, alchemical_potential)) from perses.tests.utils import compute_potential_components #print("initial potential before '%s' : %f kT" % (direction, initial_potential)) #print("initial potential components: %s" % str(compute_potential_components(context))) # DEBUG # Take a single integrator step since all switching steps are unrolled in NCMCAlchemicalIntegrator. try: # Write PDB file if requested. if self.write_pdb_interval: if direction == 'insert': topology = topology_proposal.new_topology indices = topology_proposal.unique_new_atoms else: topology = topology_proposal.old_topology indices = topology_proposal.unique_old_atoms # Write atom indices that are changing import pickle filename = 'ncmc-%s-%d-atomindices.pkl' % (direction, self.nattempted) outfile = open(filename, 'wb') pickle.dump(indices, outfile) outfile.close() from simtk.openmm.app import PDBFile filename = 'ncmc-%s-%d.pdb' % (direction, self.nattempted) outfile = open(filename, 'w') PDBFile.writeHeader(topology, file=outfile) modelIndex = 0 PDBFile.writeModel(topology, context.getState(getPositions=True).getPositions(asNumpy=True), file=outfile, modelIndex=modelIndex) try: for step in range(self.nsteps): integrator.step(1) if (step+1)%self.write_pdb_interval == 0: modelIndex += 1 PDBFile.writeModel(topology, context.getState(getPositions=True).getPositions(asNumpy=True), file=outfile, modelIndex=modelIndex) except ValueError as e: # System is exploding and coordinates won't fit in PDB ATOM fields print(e) PDBFile.writeFooter(topology, file=outfile) outfile.close() else: integrator.step(self.nsteps) except Exception as e: # Trap NaNs as a special exception (allowing us to reject later, if desired) if str(e) == "Particle coordinate is nan": raise NaNException(str(e)) else: raise e # Set final context parameters. if direction == 'insert': for parameter_name in available_parameters: context.setParameter(parameter_name, 1) elif direction == 'delete': for parameter_name in available_parameters: context.setParameter(parameter_name, 0) # Compute final potential of alchemical state. final_potential = self.beta * context.getState(getEnergy=True).getPotentialEnergy() if np.isnan(final_potential): raise NaNException("Final potential of 'delete' operation is NaN") #print("final potential before '%s' : %f kT" % (direction, final_potential)) #print("final potential components: %s" % str(compute_potential_components(context))) # DEBUG #print('') # Store final positions and log acceptance probability. final_positions = context.getState(getPositions=True).getPositions(asNumpy=True) logP_NCMC = integrator.getLogAcceptanceProbability(context) # DEBUG logging.debug("NCMC logP %+10.1f | initial_total_energy %+10.1f kT | final_total_energy %+10.1f kT." % (logP_NCMC, integrator.getGlobalVariableByName('initial_total_energy'), integrator.getGlobalVariableByName('final_total_energy'))) # Clean up NCMC switching integrator. del context, integrator # Compute contribution from transforming real system to/from alchemical system. logP_alchemical_correction = self._computeAlchemicalCorrection(unmodified_system, alchemical_system, initial_positions, final_positions, direction=direction) # Compute total logP logP = logP_NCMC + logP_alchemical_correction # Clean up alchemical system. del alchemical_system # Select whether to return initial or final potential. if direction == 'insert': potential = initial_potential elif direction == 'delete': potential = final_potential # Keep track of statistics. self.nattempted += 1 # Return return [final_positions, logP, potential]
def __init__(self, objs, out_filepath): """ This class writes frames out in the PDBFormat. It supports both OpenMM topology formats and RDKit ROMol types. The molecules are ordered sequentially by the order in which they are in objs Usage: ``` topol = app.PDBFile("1dfr.pdb").topology mol_a = Chem.MolFromMolBlock("...") mol_b = Chem.MolFromMolBlock("...") writer = PDBWriter([topol, mol_a, mol_b], out.pdb) # writer header writer.write_frame(coords) # coords must be in units of angstroms writer.close() # writes footer ``` Parameters ---------- obj: list of either Chem.Mol or app.Topoolgy types Molecules of interest out_filepath: str Where we write out the PDBFile to """ assert len(objs) > 0 rd_mols = [] # super jank for obj in objs: if isinstance(obj, app.Topology): with tempfile.NamedTemporaryFile(mode='w') as fp: # write PDBFile.writeHeader(obj, fp) PDBFile.writeModel(obj, np.zeros((obj.getNumAtoms(), 3)), fp, 0) PDBFile.writeFooter(obj, fp) fp.flush() # read rd_mols.append(Chem.MolFromPDBFile(fp.name, removeHs=False)) if isinstance(obj, Chem.Mol): rd_mols.append(obj) combined_mol = rd_mols[0] for mol in rd_mols[1:]: combined_mol = Chem.CombineMols(combined_mol, mol) with tempfile.NamedTemporaryFile(mode='w') as fp: # write Chem.MolToPDBFile(combined_mol, fp.name) fp.flush() # read combined_pdb = app.PDBFile(fp.name) self.topology = combined_pdb.topology self.out_handle = open(out_filepath, "w") PDBFile.writeHeader(self.topology, self.out_handle) self.topology = self.topology self.frame_idx = 0
def close(self): """ Write footer and close. """ PDBFile.writeFooter(self.topology, self.out_handle) self.out_handle.flush()