def test_add_solvent(self): """Test using simtk.opnmm.app.Modeller to add solvent to a small molecule parameterized by template generator""" # Select a molecule to add solvent around from simtk.openmm.app import NoCutoff, Modeller from simtk import unit molecule = self.molecules[0] openmm_topology = molecule.to_topology().to_openmm() openmm_positions = molecule.conformers[0] # Try adding solvent without residue template generator; this will fail from simtk.openmm.app import ForceField forcefield = ForceField('tip3p.xml') # Add solvent to a system containing a small molecule modeller = Modeller(openmm_topology, openmm_positions) try: modeller.addSolvent(forcefield, model='tip3p', padding=6.0*unit.angstroms) except ValueError as e: pass # Create a generator that knows about a few molecules generator = self.TEMPLATE_GENERATOR(molecules=self.molecules) # Add to the forcefield object forcefield.registerTemplateGenerator(generator.generator) # Add solvent to a system containing a small molecule # This should succeed modeller.addSolvent(forcefield, model='tip3p', padding=6.0*unit.angstroms)
def _prep_sim(self, coords, external_forces=[]): try: from simtk.openmm import Platform, LangevinIntegrator, Vec3 from simtk.openmm.app import Modeller, ForceField, \ CutoffNonPeriodic, PME, Simulation, HBonds from simtk.unit import angstrom, nanometers, picosecond, \ kelvin, Quantity, molar except ImportError: raise ImportError( 'Please install PDBFixer and OpenMM in order to use ClustENM.') positions = Quantity([Vec3(*xyz) for xyz in coords], angstrom) modeller = Modeller(self._topology, positions) if self._sol == 'imp': forcefield = ForceField(*self._force_field) system = forcefield.createSystem(modeller.topology, nonbondedMethod=CutoffNonPeriodic, nonbondedCutoff=1.0 * nanometers, constraints=HBonds) if self._sol == 'exp': forcefield = ForceField(*self._force_field) modeller.addSolvent(forcefield, padding=self._padding * nanometers, ionicStrength=self._ionicStrength * molar) system = forcefield.createSystem(modeller.topology, nonbondedMethod=PME, nonbondedCutoff=1.0 * nanometers, constraints=HBonds) for force in external_forces: system.addForce(force) integrator = LangevinIntegrator(self._temp * kelvin, 1 / picosecond, 0.002 * picosecond) # precision could be mixed, but single is okay. platform = self._platform if self._platform is None else Platform.getPlatformByName( self._platform) properties = None if self._platform is None: properties = {'Precision': 'single'} elif self._platform in ['CUDA', 'OpenCL']: properties = {'Precision': 'single'} simulation = Simulation(modeller.topology, system, integrator, platform, properties) simulation.context.setPositions(modeller.positions) return simulation
''' ligand_mol = Molecule.from_file('ethanol.sdf', file_format='sdf') forcefield_kwargs = {'constraints': app.HBonds, 'rigidWater': True, 'removeCMMotion': True, 'hydrogenMass': 4 * unit.amu } system_generator = SystemGenerator( forcefields=['amber/ff14SB.xml', 'amber/tip4pew_standard.xml'], small_molecule_forcefield='gaff-2.11', molecules=[ligand_mol], forcefield_kwargs=forcefield_kwargs) ligand_pdb = PDBFile('ethanol.pdb') modeller = Modeller(ligand_pdb.topology, ligand_pdb.positions) modeller.addSolvent(system_generator.forcefield, model='tip4pew', padding=12.0 * unit.angstroms) system = system_generator.forcefield.createSystem(modeller.topology, nonbondedMethod=PME, nonbondedCutoff=9.0 * unit.angstroms, constraints=HBonds) ''' ---FINISHED SYSTEM PREPARATION--- ''' ''' ---ALCHEMICAL CONFIGURATION--- define solute indexes, set up the alchemical region + factory, and specify steric/electrostatic lambda coupling for solvation ''' # determines solute indexes solute_indexes = collect_solute_indexes(modeller.topology)
io_w_no_h.set_structure(structure) io_w_no_h.save(f'{pdbid}_chain{chain}.pdb', ChainSelect(chain)) print("The fixed.pdb file with selected chain is ready.") # load pdb to Modeller pdb = PDBFile(f'{pdbid}_chain{chain}.pdb') molecule = Modeller(pdb.topology, pdb.positions) print("Done loading pdb to Modeller.") # load force field forcefield = ForceField('amber14-all.xml', 'amber14/tip3pfb.xml') print("Done loading force field.") print("OpenMM version:", version.version) # prepare system molecule.addSolvent(forcefield, padding=12 * unit.angstrom, model='tip3p', positiveIon='Na+', negativeIon='Cl-', ionicStrength=0 * unit.molar) print("Done adding solvent.") PDBxFile.writeFile(molecule.topology, molecule.positions, open(f'{pdbid}_chain{chain}.pdbx', 'w'), keepIds=True) PDBFile.writeFile(molecule.topology, molecule.positions, open(f'{pdbid}_chain{chain}_solvated.pdb', 'w'), keepIds=True) print("Done outputing pdbx and solvated pdb.") system = forcefield.createSystem(molecule.topology, nonbondedMethod=PME, rigidWater=True,
class MoleculeUtil(object): """ A class for managing a molecule defined by a PDB file """ np.random.seed(20) def __init__(self, pdb_path, offset_size=2): # OpenMM init self.pdb_path = pdb_path self.pdb = PDBFile(self.pdb_path) self.forcefield = ForceField('amber14-all.xml', 'amber14/tip3pfb.xml') self.modeller = Modeller(self.pdb.topology, self.pdb.positions) # Remove any water that might be present in the PDB file self.modeller.deleteWater() # Add any hydrogens not present self.modeller.addHydrogens(self.forcefield) self.system = self.forcefield.createSystem(self.modeller.topology, nonbondedMethod=PME, nonbondedCutoff=1 * u.nanometer, constraints=HBonds) self.integrator = LangevinIntegrator(300 * u.kelvin, 1 / u.picosecond, 0.002 * u.picoseconds) self.simulation = Simulation(self.modeller.topology, self.system, self.integrator) self.pdb_positions = self.modeller.getPositions() # Initialize bond dictionary and positions for chemcoord self.cc_bonds = {} self.offset_size = offset_size self._init_pdb_bonds() self.set_cc_positions(self.pdb_positions) # Perform initial minimization, which updates self.pdb_positions min_energy, min_positions = self.run_simulation() # Reset the positions after the minimization self.set_cc_positions(self.pdb_positions) self.torsion_indices = self._get_torsion_indices() self.starting_positions = min_positions self.starting_torsions = np.array([ self.zmat.loc[self.torsion_indices[:, 0], 'dihedral'], self.zmat.loc[self.torsion_indices[:, 1], 'dihedral'] ]).T self.seed_offsets() def _add_backbone_restraint(self): # https://github.com/ParmEd/ParmEd/wiki/OpenMM-Tricks-and-Recipes#positional-restraints positions = self.modeller.getPositions() force = CustomExternalForce('k*((x-x0)^2+(y-y0)^2+(z-z0)^2)') force.addGlobalParameter( 'k', 5.0 * u.kilocalories_per_mole / u.angstroms**2) force.addPerParticleParameter('x0') force.addPerParticleParameter('y0') force.addPerParticleParameter('z0') for index, atom in enumerate(self.modeller.topology.atoms()): if atom.name in ('CA', 'C', 'N'): coord = positions[index] force.addParticle(index, coord.value_in_unit(u.nanometers)) self.restraint_force_id = self.system.addForce(force) def _remove_backbone_restraint(self): self.system.removeForce(self.restraint_force_id) def _fix_backbone(self): for index, atom in enumerate(self.modeller.topology.atoms()): if atom.name in ('CA', 'C', 'N'): self.system.setParticleMass(index, 0) def seed_offsets(self): self.offsets = np.random.choice([0, 0, -1, 1], self.starting_torsions.shape) def get_torsions(self): return np.array([ self.zmat.loc[self.torsion_indices[:, 0], 'dihedral'], self.zmat.loc[self.torsion_indices[:, 1], 'dihedral'] ]).T def set_torsions(self, new_torsions): self.zmat.safe_loc[self.torsion_indices[:, 0], 'dihedral'] = new_torsions[:, 0] self.zmat.safe_loc[self.torsion_indices[:, 1], 'dihedral'] = new_torsions[:, 1] def get_offset_torsions(self, scale_factor): """ Calculates and returns new torsion angles based on randomly generated offsets. Args: scale_factor: the relative scale of the offset relative to self.offset_size Returns: The new torsion angles """ total_offset = self.offset_size * scale_factor new_torsions = np.zeros(shape=self.starting_torsions.shape) new_torsions[:, 0] = self.starting_torsions[:, 0] + \ (self.offsets[:, 0] * total_offset) new_torsions[:, 1] = self.starting_torsions[:, 1] + \ (self.offsets[:, 1] * total_offset) return new_torsions def run_simulation(self): """ Run a simulation to calculate the current configuration's energy level. Note that the atoms will likely move somewhat during the calculation, since energy minimization is used. Returns: A tuple of the form (potential_energy, updated_positions) """ # Delete solvent that's based on previous positions cartesian = self.zmat.get_cartesian().sort_index() self.simulation.context.setPositions([ Vec3(x, y, z) for x, y, z in zip(cartesian['x'], cartesian['y'], cartesian['z']) ]) # self._add_backbone_restraint() # self._fix_backbone() self.modeller.addSolvent(self.forcefield, padding=1.0 * u.nanometer) self.simulation.minimizeEnergy(maxIterations=200) state = self.simulation.context.getState(getEnergy=True, getPositions=True) p_energy = state.getPotentialEnergy() positions = state.getPositions(asNumpy=True) # Clean up - remove solvent and backbone restraint (for next iteration) self.modeller.deleteWater() # self._remove_backbone_restraint() return p_energy, positions def _init_pdb_bonds(self): """Construct a dictionary describing the PDB's bonds for chemcoord use""" for index in range(self.modeller.topology.getNumAtoms()): self.cc_bonds[index] = set() for bond in self.modeller.topology.bonds(): self.cc_bonds[bond[0].index].add(bond[1].index) self.cc_bonds[bond[1].index].add(bond[0].index) def set_cc_positions(self, positions): """ Calculates the zmat from an OpenMM modeller Args: positions (list): A list """ cc_df = self._get_cartesian_df(positions) self.cartesian = cc.Cartesian(cc_df) self.cartesian.set_bonds(self.cc_bonds) self.cartesian._give_val_sorted_bond_dict(use_lookup=True) self.zmat = self.cartesian.get_zmat(use_lookup=True) def _get_cartesian_df(self, positions): cc_positions = np.zeros((3, self.modeller.topology.getNumAtoms())) atom_names = [] for index, atom in enumerate(self.modeller.topology.atoms()): pos = positions[index] / u.nanometer atom_names.append(atom.name) cc_positions[:, index] = pos cc_df = pd.DataFrame({ 'atom': atom_names, 'x': cc_positions[0, :], 'y': cc_positions[1, :], 'z': cc_positions[2, :] }) return cc_df def _get_torsion_indices(self): """ Calculates indices into the zmatrix which correspond to phi and psi angles. Args: zmat: the zmatrix specifying the molecule Returns: a numpy.array, with first column as phi_indices, second column as psi_indices """ phi_indices = [] psi_indices = [] for i in range(len(self.zmat.index)): b_index = self.zmat.loc[i, 'b'] a_index = self.zmat.loc[i, 'a'] d_index = self.zmat.loc[i, 'd'] # If this molecule references a magic string (origin, e_x, e_y, e_z, etc) if isinstance(b_index, str) or isinstance( a_index, str) or isinstance(d_index, str): continue # Psi angles if (self.zmat.loc[i, 'atom'] == 'N') & \ (self.zmat.loc[b_index, 'atom'] == 'CA') & \ (self.zmat.loc[a_index, 'atom'] == 'C') & \ (self.zmat.loc[d_index, 'atom'] == 'N'): psi_indices.append(i) elif (self.zmat.loc[i, 'atom'] == 'N') & \ (self.zmat.loc[b_index, 'atom'] == 'C') & \ (self.zmat.loc[a_index, 'atom'] == 'CA') & \ (self.zmat.loc[d_index, 'atom'] == 'N'): psi_indices.append(i) elif (self.zmat.loc[i, 'atom'] == 'C') & \ (self.zmat.loc[b_index, 'atom'] == 'N') & \ (self.zmat.loc[a_index, 'atom'] == 'CA') & \ (self.zmat.loc[d_index, 'atom'] == 'C'): phi_indices.append(i) elif (self.zmat.loc[i, 'atom'] == 'C') & \ (self.zmat.loc[b_index, 'atom'] == 'CA') & \ (self.zmat.loc[a_index, 'atom'] == 'N') & \ (self.zmat.loc[d_index, 'atom'] == 'C'): phi_indices.append(i) return np.array([phi_indices, psi_indices]).T
# reading the ligand gives lots of warnings about "duplicate atom" but this seems incorrect print('Reading ligand') ligand_pdb = PDBFile('ligand1.pdb') print('Preparing complex') modeller = Modeller(protein_pdb.topology, protein_pdb.positions) print('System has %d atoms' % modeller.topology.getNumAtoms()) modeller.add(ligand_pdb.topology, ligand_pdb.positions) print('System has %d atoms' % modeller.topology.getNumAtoms()) box_vectors = unit.Quantity(np.diag([100, 100, 100]), unit.angstrom) modeller.topology.setPeriodicBoxVectors(box_vectors) # Solvate print('Adding solvent...') modeller.addSolvent(system_generator.forcefield, model='tip3p') #, padding=5.0*unit.angstroms) print('System has %d atoms' % modeller.topology.getNumAtoms()) PDBFile.writeFile(modeller.topology, modeller.positions, open('complex1.pdb', 'w')) complex_pdb = PDBFile('complex1.pdb') system = system_generator.create_system(complex_pdb.topology, molecules=ligand_mol) integrator = LangevinIntegrator(300 * unit.kelvin, 1 / unit.picosecond, 0.002 * unit.picoseconds) print('Uses Periodic box:', system.usesPeriodicBoundaryConditions()) print('Default Periodic box:', system.getDefaultPeriodicBoxVectors()) simulation = Simulation(complex_pdb.topology,
# Modeller needs topology and positions. Lots of trial and error found that this is what works to get these from # an openforcefield Molecule object that was created from a RDKit molecule. # The topology part is described in the openforcefield API but the positions part grabs the first (and only) # conformer and passes it to Modeller. It works. Don't ask why! if len(other_mols) != 0: for other_mol in other_mols: modeller.add(other_mol.to_topology().to_openmm(), other_mol.conformers[0]) #modeller.add(ligand_mol.to_topology().to_openmm(), ligand_mol.conformers[0]) # Generate ligand with solvent for FEP if opt.ligand: modeller_org = Modeller(ligand_mol.to_topology().to_openmm(), ligand_mol.conformers[0]) modeller_org.addSolvent(system_generator.forcefield, model='tip3p', ionicStrength=0.1 * unit.molar, padding=10.0 * unit.angstroms) system_org = system_generator.create_system(modeller_org.topology, molecules=ligand_mol) system_org.addForce( openmm.MonteCarloBarostat(1 * unit.atmospheres, opt.temp * unit.kelvin, 25)) print('System has %d atoms' % modeller.topology.getNumAtoms()) # Solvate print('Adding solvent...') # we use the 'padding' option to define the periodic box. The PDB file does not contain any # unit cell information so we just create a box that has a 10A padding around the complex. #modeller.addSolvent(system_generator.forcefield, model='tip3p', ionicStrength=0.1*unit.molar, padding=10.0*unit.angstroms) modeller.addSolvent(system_generator.forcefield,