def _via_helper_water(cls, **kwargs): """ Helper function for via_rdkit or via_openeye Returns ------------------ system_pmd : parmed.structure The parameterised system as parmed object """ from pdbfixer import PDBFixer # for solvating fixer = PDBFixer(cls.pdb_filename) if "padding" not in kwargs: fixer.addSolvent(padding=cls.default_padding) else: fixer.addSolvent(padding=float(kwargs["padding"])) tmp_dir = tempfile.mkdtemp() cls.pdb_filename = tempfile.mktemp(suffix=".pdb", dir=tmp_dir) with open(cls.pdb_filename, "w") as f: PDBFile.writeFile(fixer.topology, fixer.positions, f) complex = parmed.load_file(cls.pdb_filename) solvent = complex["(:HOH)"] num_solvent = len(solvent.residues) solvent_pmd = cls.solvent_pmd * num_solvent solvent_pmd.positions = solvent.positions cls.system_pmd = cls.ligand_pmd + solvent_pmd cls.system_pmd.box_vectors = complex.box_vectors try: shutil.rmtree("/".join(cls.pdb_filename.split("/")[:-1])) del cls.ligand_pmd except: pass cls.system_pmd.title = cls.smiles return cls.system_pmd
def add_solvent(pdb_filepath: str, ani_input: dict, pdb_output_filepath: str, box_length: unit.quantity.Quantity = (2.5 * unit.nanometer)): assert (type(box_length) == unit.Quantity) pdb = PDBFixer(filename=pdb_filepath) # Step 0: put the ligand in the center #pdb.positions = np.array(pdb.positions.value_in_unit(unit.nanometer)) + box_length/2 # add water l = box_length.value_in_unit(unit.nanometer) pdb.addSolvent(boxVectors=(Vec3(l, 0.0, 0.0), Vec3(0.0, l, 0.0), Vec3(0.0, 0.0, l))) # Step 1: convert coordinates from standard cartesian coordinate to unit # cell coordinates #inv_cell = 1/box_length #coordinates_cell = np.array(pdb.positions.value_in_unit(unit.nanometer)) * inv_cell # Step 2: wrap cell coordinates into [0, 1) #coordinates_cell -= np.floor(coordinates_cell) # Step 3: convert back to coordinates #coordinates_cell = (coordinates_cell * box_length) * unit.nanometer #pdb.positions = coordinates_cell from simtk.openmm.app import PDBFile PDBFile.writeFile(pdb.topology, pdb.positions, open(pdb_output_filepath, 'w')) atom_list = [] coord_list = [] for atom, coor in zip(pdb.topology.atoms(), pdb.positions): if atom.residue.name != 'HOH': continue atom_list.append(atom.element.symbol) coor = coor.value_in_unit(unit.angstrom) coord_list.append([coor[0], coor[1], coor[2]]) ani_input['solvent_atoms'] = ''.join(atom_list) ani_input['solvent_coords'] = np.array(coord_list) * unit.angstrom ani_input['box_length'] = box_length
print('Adding missing atoms...') fixer.findMissingAtoms() fixer.addMissingAtoms() # Remove heterogens. print('Removing heterogens...') fixer.removeHeterogens(keepWater=keepWater) # Add missing hydrogens. print('Adding missing hydrogens appropriate for pH %s' % pH) fixer.addMissingHydrogens(pH) if nonbondedMethod in [app.PME, app.CutoffPeriodic, app.Ewald]: # Add solvent. print('Adding solvent...') fixer.addSolvent(padding=padding) # Write PDB file. output_filename = '%s-pdbfixer.pdb' % pdbid print('Writing PDB file to "%s"...' % output_filename) app.PDBFile.writeFile(fixer.topology, fixer.positions, open(output_filename, 'w')) # Create OpenMM System. print('Creating OpenMM system...') system = forcefield.createSystem(fixer.topology, nonbondedMethod=nonbondedMethod, constraints=constraints, rigidWater=True, removeCMMotion=False) # Minimimze to update positions. print('Minimizing...') integrator = openmm.VerletIntegrator(1.0 * unit.femtosecond) context = openmm.Context(system, integrator) context.setPositions(fixer.positions)
def add_droplet( self, topology: md.Topology, coordinates: unit.quantity.Quantity, diameter: unit.quantity.Quantity = (30.0 * unit.angstrom), restrain_hydrogen_bonds: bool = True, restrain_hydrogen_angles: bool = False, top_file: str = "", ) -> md.Trajectory: """ Adding a droplet with a given diameter around a small molecule. Parameters ---------- topology: md.Topology topology of the molecule coordinates: np.array, unit'd diameter: float, unit'd top_file: str if top_file is provided the final droplet pdb is either kept and can be reused or if top_file already exists it will be used to create the same droplet. Returns ---------- A mdtraj.Trajectory object with the ligand centered in the solvent for inspection. """ assert type(diameter) == unit.Quantity assert type(topology) == md.Topology assert type(coordinates) == unit.Quantity if restrain_hydrogen_bonds: logger.debug("Hydrogen bonds are restraint.") if restrain_hydrogen_angles: logger.warning("HOH angles are restraint.") # get topology from mdtraj to PDBfixer via pdb file radius = diameter.value_in_unit(unit.angstrom) / 2 center = np.array([radius, radius, radius]) # if no solvated pdb file is provided generate one if top_file: # read in the file with the defined droplet pdb_filepath = top_file else: # generage a one time droplet pdb_filepath = f"tmp{random.randint(1,10000000)}.pdb" if not os.path.exists(pdb_filepath): logger.info(f"Generating droplet for {pdb_filepath}...") # mdtraj works with nanomter md.Trajectory(coordinates.value_in_unit(unit.nanometer), topology).save_pdb(pdb_filepath) pdb = PDBFixer(filename=pdb_filepath) os.remove(pdb_filepath) # put the ligand in the center l_in_nanometer = diameter.value_in_unit(unit.nanometer) pdb.positions = np.array( pdb.positions.value_in_unit( unit.nanometer)) + (l_in_nanometer / 2) # add water pdb.addSolvent(boxVectors=( Vec3(l_in_nanometer, 0.0, 0.0), Vec3(0.0, l_in_nanometer, 0.0), Vec3(0.0, 0.0, l_in_nanometer), )) # get topology from PDBFixer to mdtraj # NOTE: a second tmpfile - not happy about this from simtk.openmm.app import PDBFile PDBFile.writeFile(pdb.topology, pdb.positions, open(pdb_filepath, "w")) # load pdb in parmed logger.debug("Load with parmed ...") structure = pm.load_file(pdb_filepath) os.remove(pdb_filepath) # search for residues that are outside of the cutoff and delete them to_delete = [] logger.debug("Flag residues ...") for residue in structure.residues: for atom in residue: p1 = np.array([atom.xx, atom.xy, atom.xz]) p2 = center squared_dist = np.sum((p1 - p2)**2, axis=0) dist = np.sqrt(squared_dist) if ( dist > radius + 1 ): # NOTE: distance must be greater than radius + 1 Angstrom to_delete.append(residue) # only delete water molecules for residue in list(set(to_delete)): if residue.name == "HOH": logger.debug(f"Remove: {residue}") structure.residues.remove(residue) else: logger.warning( f"Residue {residue} reaches outside the droplet") print(f"Residue {residue} reaches outside the droplet") structure.write_pdb(pdb_filepath) # load pdb with mdtraj traj = md.load(pdb_filepath) if not top_file: os.remove(pdb_filepath) # set coordinates #NOTE: note the xyz[0] self._ligand_in_water_coordinates = traj.xyz[0] * unit.nanometer # generate atom string atom_list = [] for atom in traj.topology.atoms: atom_list.append(atom.element.symbol) # set atom string self.ligand_in_water_atoms = "".join(atom_list) # set mdtraj topology self.ligand_in_water_topology = traj.topology # set FlattBottomRestraintToCenter on each oxygen self.solvent_restraints = [] for residue in traj.topology.residues: if residue.is_water: for atom in residue.atoms: if str(atom.element.symbol) == "O": self.solvent_restraints.append( CenterFlatBottomRestraint( sigma=0.1 * unit.angstrom, point=center * unit.angstrom, radius=(diameter / 2), atom_idx=atom.index, active_at=-1, )) logger.debug("Adding restraint to center to {}".format( atom.index)) if restrain_hydrogen_bonds or restrain_hydrogen_angles: for residue in traj.topology.residues: if residue.is_water: oxygen_idx = -1 hydrogen_idxs = [] for atom in residue.atoms: if str(atom.element.symbol) == "O": oxygen_idx = atom.index elif str(atom.element.symbol) == "H": hydrogen_idxs.append(atom.index) else: raise RuntimeError( "Water should only consist of O and H atoms.") if restrain_hydrogen_bonds: self.solvent_restraints.append( BondFlatBottomRestraint( sigma=0.2 * unit.angstrom, atom_i_idx=oxygen_idx, atom_j_idx=hydrogen_idxs[0], atoms=self.ligand_in_water_atoms, )) self.solvent_restraints.append( BondFlatBottomRestraint( sigma=0.2 * unit.angstrom, atom_i_idx=oxygen_idx, atom_j_idx=hydrogen_idxs[1], atoms=self.ligand_in_water_atoms, )) if restrain_hydrogen_angles: self.solvent_restraints.append( AngleHarmonicRestraint( sigma=0.1 * unit.radian, atom_i_idx=hydrogen_idxs[0], atom_j_idx=oxygen_idx, atom_k_idx=hydrogen_idxs[1], )) # return a mdtraj object for visual check return md.Trajectory( self._ligand_in_water_coordinates.value_in_unit(unit.nanometer), self.ligand_in_water_topology, )
print('Adding missing atoms...') fixer.findMissingAtoms() fixer.addMissingAtoms() # Remove heterogens. print('Removing heterogens...') fixer.removeHeterogens(keepWater=keepWater) # Add missing hydrogens. print('Adding missing hydrogens appropriate for pH %s' % pH) fixer.addMissingHydrogens(pH) if nonbondedMethod in [app.PME, app.CutoffPeriodic, app.Ewald]: # Add solvent. print('Adding solvent...') fixer.addSolvent(padding=padding) # Write PDB file. output_filename = '%s-pdbfixer.pdb' % pdbid print('Writing PDB file to "%s"...' % output_filename) app.PDBFile.writeFile(fixer.topology, fixer.positions, open(output_filename, 'w')) # Create OpenMM System. print('Creating OpenMM system...') system = forcefield.createSystem(fixer.topology, nonbondedMethod=nonbondedMethod, constraints=constraints, rigidWater=True, removeCMMotion=False)
def hydrate(system, opt): """ This function solvates the system by using PDBFixer Parameters: ----------- system: OEMol molecule The system to solvate opt: python dictionary The parameters used to solvate the system Return: ------- oe_mol: OEMol The solvated system """ def BoundingBox(molecule): """ This function calculates the Bounding Box of the passed molecule molecule: OEMol return: bb (numpy array) the calculated bounding box is returned as numpy array: [(xmin,ymin,zmin), (xmax,ymax,zmax)] """ coords = [v for k, v in molecule.GetCoords().items()] np_coords = np.array(coords) min_coord = np_coords.min(axis=0) max_coord = np_coords.max(axis=0) bb = np.array([min_coord, max_coord]) return bb # Create a system copy sol_system = system.CreateCopy() # Calculate system BoundingBox (Angstrom units) BB = BoundingBox(sol_system) # Estimation of the box cube length in A box_edge = 2.0 * opt['solvent_padding'] + np.max(BB[1] - BB[0]) # BB center xc = (BB[0][0]+BB[1][0])/2. yc = (BB[0][1]+BB[1][1])/2. zc = (BB[0][2]+BB[1][2])/2. delta = np.array([box_edge/2., box_edge/2., box_edge/2.]) - np.array([xc, yc, zc]) sys_coord_dic = {k: (v+delta) for k, v in sol_system.GetCoords().items()} sol_system.SetCoords(sys_coord_dic) # Load a fake system to initialize PDBfixer filename = resource_filename('pdbfixer', 'tests/data/test.pdb') fixer = PDBFixer(filename=filename) # Convert between OE and OpenMM topology omm_top, omm_pos = oeommutils.oemol_to_openmmTop(sol_system) chain_names = [] for chain in omm_top.chains(): chain_names.append(chain.id) # Set the correct topology to the fake system fixer.topology = omm_top fixer.positions = omm_pos # Solvate the system fixer.addSolvent(padding=unit.Quantity(opt['solvent_padding'], unit.angstroms), ionicStrength=unit.Quantity(opt['salt_concentration'], unit.millimolar)) # The OpenMM topology produced by the solvation fixer has missing bond # orders and aromaticity. The following section is creating a new openmm # topology made of just water molecules and ions. The new topology is then # converted in an OEMol and added to the passed molecule to produce the # solvated system wat_ion_top = app.Topology() # Atom dictionary between the the PDBfixer topology and the water_ion topology fixer_atom_to_wat_ion_atom = {} for chain in fixer.topology.chains(): if chain.id not in chain_names: n_chain = wat_ion_top.addChain(chain.id) for res in chain.residues(): n_res = wat_ion_top.addResidue(res.name, n_chain) for at in res.atoms(): n_at = wat_ion_top.addAtom(at.name, at.element, n_res) fixer_atom_to_wat_ion_atom[at] = n_at for bond in fixer.topology.bonds(): at0 = bond[0] at1 = bond[1] try: wat_ion_top.addBond(fixer_atom_to_wat_ion_atom[at0], fixer_atom_to_wat_ion_atom[at1], type=None, order=1) except: pass wat_ion_pos = fixer.positions[len(omm_pos):] oe_mol = oeommutils.openmmTop_to_oemol(wat_ion_top, wat_ion_pos) # Setting the box vectors omm_box_vectors = fixer.topology.getPeriodicBoxVectors() box_vectors = utils.PackageOEMol.encodePyObj(omm_box_vectors) oe_mol.SetData(oechem.OEGetTag('box_vectors'), box_vectors) oechem.OEAddMols(oe_mol, sol_system) return oe_mol
def solvate(system, opt): """ This function solvates the system by using PDBFixer Parameters: ----------- system: OEMol molecule The system to solvate opt: python dictionary The parameters used to solvate the system Return: ------- oe_mol: OEMol The solvated system """ # Load a fake system to initialize PDBfixer filename = resource_filename('pdbfixer', 'tests/data/test.pdb') fixer = PDBFixer(filename=filename) # Convert between OE and OpenMM topology omm_top, omm_pos = oeommutils.oemol_to_openmmTop(system) chain_names = [] for chain in omm_top.chains(): chain_names.append(chain.id) # Set the correct topology to the fake system fixer.topology = omm_top fixer.positions = omm_pos # Solvate the system fixer.addSolvent(padding=unit.Quantity(opt['solvent_padding'], unit.angstroms), ionicStrength=unit.Quantity(opt['salt_concentration'], unit.millimolar)) # The OpenMM topology produced by the solvation fixer has missing bond # orders and aromaticity. The following section is creating a new openmm # topology made of just water molecules and ions. The new topology is then # converted in an OEMol and added to the passed molecule to produce the # solvated system wat_ion_top = app.Topology() # Atom dictionary between the the PDBfixer topology and the water_ion topology fixer_atom_to_wat_ion_atom = {} for chain in fixer.topology.chains(): if chain.id not in chain_names: n_chain = wat_ion_top.addChain(chain.id) for res in chain.residues(): n_res = wat_ion_top.addResidue(res.name, n_chain) for at in res.atoms(): n_at = wat_ion_top.addAtom(at.name, at.element, n_res) fixer_atom_to_wat_ion_atom[at] = n_at for bond in fixer.topology.bonds(): at0 = bond[0] at1 = bond[1] try: wat_ion_top.addBond(fixer_atom_to_wat_ion_atom[at0], fixer_atom_to_wat_ion_atom[at1], type=None, order=1) except: pass wat_ion_pos = fixer.positions[len(omm_pos):] oe_mol = oeommutils.openmmTop_to_oemol(wat_ion_top, wat_ion_pos) # Setting the box vectors omm_box_vectors = fixer.topology.getPeriodicBoxVectors() box_vectors = utils.PackageOEMol.encodePyObj(omm_box_vectors) oe_mol.SetData(oechem.OEGetTag('box_vectors'), box_vectors) oechem.OEAddMols(oe_mol, system) return oe_mol
import pdbfixer from pdbfixer import PDBFixer from simtk import unit from simtk.openmm.app import PDBFile output_file = 't_h.pdb' fixer = PDBFixer(filename='VER_apo.pdb') fixer.findMissingResidues() fixer.findMissingAtoms() fixer.addMissingAtoms() fixer.addMissingHydrogens(pH=7.5) fixer.addSolvent(padding=11*unit.angstrom, ionicStrength=0.050*unit.molar) #PDBFile.writeHeader(fixer.topology, open(output_file, 'w')) PDBFile.writeFile(fixer.topology, fixer.positions, open(output_file, 'w')) #PDBFile.writeFooter(fixer.topology, open(output_file, 'a'))
def prepare_pdb(pdb, chains='A', ff=('amber99sbildn.xml', 'tip3p.xml'), ph=7, pad=10 * unit.angstroms, nbonded=app.PME, constraints=app.HBonds, crystal_water=True): """ Fetch, solvate and minimize a protein PDB structure. Parameters ---------- pdb : str PDB Id. chains : str or list Chain(s) to keep in the system. ff : tuple of xml ff files. Forcefields for parametrization. ph : float pH value for adding missing hydrogens. pad: Quantity object Padding around macromolecule for filling box with water. nbonded : object The method to use for nonbonded interactions. Allowed values are NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, PME, or LJPME. constraints : object Specifies which bonds and angles should be implemented with constraints. Allowed values are None, HBonds, AllBonds, or HAngles. crystal_water : bool Keep crystal water. """ # Load forcefield. logger.info('Retrieving %s from PDB...', pdb) ff = app.ForceField(*ff) # Retrieve structure from PDB. fixer = PDBFixer(pdbid=pdb) # Remove unselected chains. logger.info('Removing all chains but %s', chains) all_chains = [c.id for c in fixer.topology.chains()] fixer.removeChains(chainIds=set(all_chains) - set(chains)) # Find missing residues. logger.info('Finding missing residues...') fixer.findMissingResidues() # Replace nonstandard residues. logger.info('Replacing nonstandard residues...') fixer.findNonstandardResidues() fixer.replaceNonstandardResidues() # Add missing atoms. logger.info('Adding missing atoms...') fixer.findMissingAtoms() fixer.addMissingAtoms() # Remove heterogens. logger.info('Removing heterogens...') fixer.removeHeterogens(keepWater=crystal_water) # Add missing hydrogens. logger.info('Adding missing hydrogens appropriate for pH %s', ph) fixer.addMissingHydrogens(ph) if nbonded in [app.PME, app.CutoffPeriodic, app.Ewald]: # Add solvent. logger.info('Adding solvent...') fixer.addSolvent(padding=pad) # Write PDB file. logger.info('Writing PDB file to "%s"...', '%s-pdbfixer.pdb' % pdb) app.PDBFile.writeFile(fixer.topology, fixer.positions, open('%s-pdbfixer.pdb' % pdb, 'w')) # Create OpenMM System. logger.info('Creating OpenMM system...') system = ff.createSystem(fixer.topology, nonbondedMethod=nbonded, constraints=constraints, rigidWater=True, removeCMMotion=False) # Minimimze to update positions. logger.info('Minimizing...') integrator = mm.VerletIntegrator(1.0 * unit.femtosecond) context = mm.Context(system, integrator) context.setPositions(fixer.positions) mm.LocalEnergyMinimizer.minimize(context) # pylint: disable=unexpected-keyword-arg, no-value-for-parameter state = context.getState(getPositions=True) fixer.positions = state.getPositions() # Write final coordinates. logger.info('Writing PDB file to "%s"...', '%s-minimized.pdb' % pdb) with open('%s-minimized.pdb' % pdb, 'w') as fp: app.PDBFile.writeFile(fixer.topology, fixer.positions, fp) # Serialize final coordinates. logger.info('Serializing to XML...') serialize_system(context, system, integrator)