def test_parameterize(self): """Test parameterizing molecules with template generator for all supported force fields""" # Test all supported small molecule force fields for small_molecule_forcefield in self.TEMPLATE_GENERATOR.INSTALLED_FORCEFIELDS: print(f'Testing {small_molecule_forcefield}') # Create a generator that knows about a few molecules # TODO: Should the generator also load the appropriate force field files into the ForceField object? generator = self.TEMPLATE_GENERATOR(molecules=self.molecules, forcefield=small_molecule_forcefield) # Check that we have loaded the right force field assert generator.forcefield == small_molecule_forcefield # Create a ForceField with the appropriate small molecule force field from simtk.openmm.app import ForceField forcefield = ForceField() # Register the template generator forcefield.registerTemplateGenerator(generator.generator) # Parameterize some molecules from simtk.openmm.app import NoCutoff from openmmforcefields.utils import Timer for molecule in self.molecules: openmm_topology = molecule.to_topology().to_openmm() with Timer() as t1: system = forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff) assert system.getNumParticles() == molecule.n_atoms # Molecule should now be cached with Timer() as t2: system = forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff) assert system.getNumParticles() == molecule.n_atoms assert (t2.interval() < t1.interval())
def test_debug_ffxml(self): """Test that debug ffxml file is created when requested""" with tempfile.TemporaryDirectory() as tmpdirname: debug_ffxml_filename = os.path.join(tmpdirname, 'molecule.ffxml') cache = os.path.join(tmpdirname, 'db.json') # Create a generator that only knows about one molecule molecule = self.molecules[0] generator = self.TEMPLATE_GENERATOR(molecules=molecule, cache=cache) # Create a ForceField from simtk.openmm.app import ForceField forcefield = ForceField() # Register the template generator forcefield.registerTemplateGenerator(generator.generator) # Ensure no file is created from simtk.openmm.app import NoCutoff openmm_topology = molecule.to_topology().to_openmm() system = forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff) assert not os.path.exists(debug_ffxml_filename) # Enable debug file output creation forcefield = ForceField() forcefield.registerTemplateGenerator(generator.generator) generator.debug_ffxml_filename = debug_ffxml_filename # Ensure that an ffxml file is created system = forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff) assert os.path.exists(debug_ffxml_filename) # Ensure we can use that file to create a new force field forcefield_from_ffxml = ForceField() if hasattr(generator, 'gaff_xml_filename'): forcefield_from_ffxml.loadFile(generator.gaff_xml_filename) forcefield_from_ffxml.loadFile(debug_ffxml_filename) system2 = forcefield_from_ffxml.createSystem(openmm_topology, nonbondedMethod=NoCutoff) # TODO: Test that systems are equivalent assert system.getNumParticles() == system2.getNumParticles()
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 test_charge(self): """Test that charges are nonzero after charging if the molecule does not contain user charges""" # Create a generator that does not know about any molecules generator = self.TEMPLATE_GENERATOR() # Create a ForceField from simtk.openmm.app import ForceField forcefield = ForceField() # Register the template generator forcefield.registerTemplateGenerator(generator.generator) # Check that parameterizing a molecule using user-provided charges produces expected charges import numpy as np from simtk import unit molecule = self.molecules[0] # Ensure partial charges are initially zero assert np.all(molecule.partial_charges / unit.elementary_charge == 0) # Add the molecule generator.add_molecules(molecule) # Create the System from simtk.openmm.app import NoCutoff openmm_topology = molecule.to_topology().to_openmm() system = forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff) # Ensure charges are no longer zero assert not np.all( self.charges_from_system(system) == 0), "System has zero charges despite molecule not being charged"
def test_charge_from_molecules(self): """Test that user-specified partial charges are used if requested""" # Create a generator that does not know about any molecules generator = self.TEMPLATE_GENERATOR() # Create a ForceField from simtk.openmm.app import ForceField forcefield = ForceField() # Register the template generator forcefield.registerTemplateGenerator(generator.generator) # Check that parameterizing a molecule using user-provided charges produces expected charges import numpy as np from simtk import unit molecule = self.molecules[0] charges = np.random.random([molecule.n_particles]) charges += (molecule.total_charge - charges.sum()) / molecule.n_particles molecule.partial_charges = unit.Quantity(charges, unit.elementary_charge) assert not np.all(molecule.partial_charges / unit.elementary_charge == 0) # Add the molecule generator.add_molecules(molecule) # Create the System from simtk.openmm.app import NoCutoff openmm_topology = molecule.to_topology().to_openmm() system = forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff) assert self.charges_are_equal(system, molecule)
def imatinib_timing(): print("Loading imatinib...") # Load the PDB file. from simtk.openmm.app import PDBFile pdb_filename = utils.get_data_filename("chemicals/imatinib/imatinib.pdb") pdb = PDBFile(pdb_filename) # Create a ForceField object. gaff_xml_filename = utils.get_data_filename("parameters/gaff.xml") forcefield = ForceField(gaff_xml_filename) # Add the residue template generator. from openmoltools.forcefield_generators import gaffTemplateGenerator forcefield.registerTemplateGenerator(gaffTemplateGenerator) # Parameterize system. system = forcefield.createSystem(pdb.topology, nonbondedMethod=NoCutoff) integrator = openmm.LangevinIntegrator(300 * unit.kelvin, 5.0 / unit.picoseconds, 1.0 * unit.femtoseconds) # Create Context context = openmm.Context(system, integrator) context.setPositions(pdb.positions) integrator.step(100) import time nsteps = 10000000 initial_time = time.time() integrator.step(nsteps) state = context.getState().getPeriodicBoxVectors() # force dynamics final_time = time.time() elapsed_time = final_time / initial_time time_per_step = elapsed_time / float(nsteps) print('time per force evaluation is %.3f us' % (time_per_step*1e6))
def imatinib_timing(): print("Loading imatinib...") # Load the PDB file. from simtk.openmm.app import PDBFile pdb_filename = utils.get_data_filename("chemicals/imatinib/imatinib.pdb") pdb = PDBFile(pdb_filename) # Create a ForceField object. gaff_xml_filename = utils.get_data_filename("parameters/gaff.xml") forcefield = ForceField(gaff_xml_filename) # Add the residue template generator. from openmoltools.forcefield_generators import gaffTemplateGenerator forcefield.registerTemplateGenerator(gaffTemplateGenerator) # Parameterize system. system = forcefield.createSystem(pdb.topology, nonbondedMethod=NoCutoff) integrator = openmm.LangevinIntegrator(300 * unit.kelvin, 5.0 / unit.picoseconds, 1.0 * unit.femtoseconds) # Create Context context = openmm.Context(system, integrator) context.setPositions(pdb.positions) integrator.step(100) import time nsteps = 10000000 initial_time = time.time() integrator.step(nsteps) state = context.getState().getPeriodicBoxVectors() # force dynamics final_time = time.time() elapsed_time = final_time / initial_time time_per_step = elapsed_time / float(nsteps) print('time per force evaluation is %.3f us' % (time_per_step * 1e6))
def test_cache(self): """Test template generator cache capability""" from simtk.openmm.app import ForceField, NoCutoff with tempfile.TemporaryDirectory() as tmpdirname: # Create a generator that also has a database cache cache = os.path.join(tmpdirname, 'db.json') generator = self.TEMPLATE_GENERATOR(molecules=self.molecules, cache=cache) # Create a ForceField forcefield = ForceField() # Register the template generator forcefield.registerTemplateGenerator(generator.generator) # Parameterize the molecules for molecule in self.molecules: openmm_topology = molecule.to_topology().to_openmm() forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff) # Check database contents def check_cache(generator, n_expected): """ Check database contains number of expected records Parameters ---------- generator : SmallMoleculeTemplateGenerator The generator whose cache should be examined n_expected : int Number of expected records """ from tinydb import TinyDB db = TinyDB(generator._cache) table = db.table(generator._database_table_name) db_entries = table.all() db.close() n_entries = len(db_entries) assert (n_entries == n_expected), \ "Expected {} entries but database has {}\n db contents: {}".format(n_expected, n_entries, db_entries) check_cache(generator, len(self.molecules)) # Clean up, forcing closure of database del forcefield, generator # Create a generator that also uses the database cache but has no molecules print('Creating new generator with just cache...') generator = self.TEMPLATE_GENERATOR(cache=cache) # Check database still contains the molecules we expect check_cache(generator, len(self.molecules)) # Create a ForceField forcefield = ForceField() # Register the template generator forcefield.registerTemplateGenerator(generator.generator) # Parameterize the molecules; this should succeed for molecule in self.molecules: openmm_topology = molecule.to_topology().to_openmm() forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff)
def test_jacs_ligands(self): """Use template generator to parameterize the Schrodinger JACS set of ligands""" from simtk.openmm.app import ForceField, NoCutoff jacs_systems = { #'bace' : { 'prefix' : 'Bace' }, #'cdk2' : { 'prefix' : 'CDK2' }, 'jnk1' : { 'prefix' : 'Jnk1' }, 'mcl1' : { 'prefix' : 'MCL1' }, #'p38' : { 'prefix' : 'p38' }, 'ptp1b' : { 'prefix' : 'PTP1B' }, 'thrombin' : { 'prefix' : 'Thrombin' }, #'tyk2' : { 'prefix' : 'Tyk2' }, } for system_name in jacs_systems: prefix = jacs_systems[system_name]['prefix'] # Load molecules ligand_sdf_filename = get_data_filename(os.path.join('perses_jacs_systems', system_name, prefix + '_ligands.sdf')) print(f'Reading molecules from {ligand_sdf_filename} ...') from openforcefield.topology import Molecule molecules = Molecule.from_file(ligand_sdf_filename, allow_undefined_stereo=True) # Ensure this is a list try: nmolecules = len(molecules) except TypeError: molecules = [molecules] print(f'Read {len(molecules)} molecules from {ligand_sdf_filename}') #molecules = self.filter_molecules(molecules) MAX_MOLECULES = len(molecules) if 'TRAVIS' in os.environ: MAX_MOLECULES = 3 molecules = molecules[:MAX_MOLECULES] print(f'{len(molecules)} molecules remain after filtering') # Create template generator with local cache cache = os.path.join(get_data_filename(os.path.join('perses_jacs_systems', system_name)), 'cache.json') generator = self.TEMPLATE_GENERATOR(molecules=molecules, cache=cache) # Create a ForceField forcefield = ForceField() # Register the template generator forcefield.registerTemplateGenerator(generator.generator) # Parameterize all molecules print(f'Caching all molecules for {system_name} at {cache} ...') n_success = 0 n_failure = 0 for molecule in molecules: openmm_topology = molecule.to_topology().to_openmm() try: forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff) n_success += 1 except Exception as e: n_failure += 1 print(e) print(f'{n_failure}/{n_success+n_failure} ligands failed to parameterize for {system_name}')
def test_gaffResidueTemplateGenerator(): """ Test the GAFF residue template generator. """ # # Test where we generate parameters for only a ligand. # # Load the PDB file. from simtk.openmm.app import PDBFile pdb_filename = utils.get_data_filename("chemicals/imatinib/imatinib.pdb") pdb = PDBFile(pdb_filename) # Create a ForceField object. gaff_xml_filename = utils.get_data_filename("parameters/gaff.xml") forcefield = ForceField(gaff_xml_filename) # Add the residue template generator. from openmoltools.forcefield_generators import gaffTemplateGenerator forcefield.registerTemplateGenerator(gaffTemplateGenerator) # Parameterize system. system = forcefield.createSystem(pdb.topology, nonbondedMethod=NoCutoff) # Check potential is finite. check_potential_is_finite(system, pdb.positions) # Check energy matches prmtop route. check_energy_components_vs_prmtop( prmtop=utils.get_data_filename("chemicals/imatinib/imatinib.prmtop"), inpcrd=utils.get_data_filename("chemicals/imatinib/imatinib.inpcrd"), system=system, ) # # Test where we generate parameters for only a ligand in a protein. # # Load the PDB file. from simtk.openmm.app import PDBFile pdb_filename = utils.get_data_filename("chemicals/proteins/T4-lysozyme-L99A-p-xylene-implicit.pdb") pdb = PDBFile(pdb_filename) # Create a ForceField object. gaff_xml_filename = utils.get_data_filename("parameters/gaff.xml") forcefield = ForceField("amber99sb.xml", gaff_xml_filename) # Add the residue template generator. from openmoltools.forcefield_generators import gaffTemplateGenerator forcefield.registerTemplateGenerator(gaffTemplateGenerator) # Parameterize system. system = forcefield.createSystem(pdb.topology, nonbondedMethod=NoCutoff) # Check potential is finite. check_potential_is_finite(system, pdb.positions)
def test_gaffResidueTemplateGenerator(): """ Test the GAFF residue template generator. """ # # Test where we generate parameters for only a ligand. # # Load the PDB file. from simtk.openmm.app import PDBFile pdb_filename = utils.get_data_filename("chemicals/imatinib/imatinib.pdb") pdb = PDBFile(pdb_filename) # Create a ForceField object. gaff_xml_filename = utils.get_data_filename("parameters/gaff.xml") forcefield = ForceField(gaff_xml_filename) # Add the residue template generator. from openmoltools.forcefield_generators import gaffTemplateGenerator forcefield.registerTemplateGenerator(gaffTemplateGenerator) # Parameterize system. system = forcefield.createSystem(pdb.topology, nonbondedMethod=NoCutoff) # Check potential is finite. check_potential_is_finite(system, pdb.positions) # Check energy matches prmtop route. check_energy_components_vs_prmtop( prmtop=utils.get_data_filename('chemicals/imatinib/imatinib.prmtop'), inpcrd=utils.get_data_filename('chemicals/imatinib/imatinib.inpcrd'), system=system) # # Test where we generate parameters for only a ligand in a protein. # # Load the PDB file. from simtk.openmm.app import PDBFile pdb_filename = utils.get_data_filename( "chemicals/proteins/T4-lysozyme-L99A-p-xylene-implicit.pdb") pdb = PDBFile(pdb_filename) # Create a ForceField object. gaff_xml_filename = utils.get_data_filename("parameters/gaff.xml") forcefield = ForceField('amber99sb.xml', gaff_xml_filename) # Add the residue template generator. from openmoltools.forcefield_generators import gaffTemplateGenerator forcefield.registerTemplateGenerator(gaffTemplateGenerator) # Parameterize system. system = forcefield.createSystem(pdb.topology, nonbondedMethod=NoCutoff) # Check potential is finite. check_potential_is_finite(system, pdb.positions)
def test_multiple_registration(self): """Test registering the template generator with multiple force fields""" generator = self.TEMPLATE_GENERATOR(molecules=self.molecules) from simtk.openmm.app import ForceField NUM_FORCEFIELDS = 2 # number of force fields to test forcefields = list() for index in range(NUM_FORCEFIELDS): forcefield = ForceField() forcefield.registerTemplateGenerator(generator.generator) forcefields.append(forcefield) # Parameterize a molecule in each force field instance molecule = self.molecules[0] openmm_topology = molecule.to_topology().to_openmm() from simtk.openmm.app import NoCutoff for forcefield in forcefields: system = forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff) assert system.getNumParticles() == molecule.n_atoms
def test_add_molecules(self): """Test that molecules can be added to template generator after its creation""" # Create a generator that does not know about any molecules generator = self.TEMPLATE_GENERATOR() # Create a ForceField from simtk.openmm.app import ForceField forcefield = ForceField() # Register the template generator forcefield.registerTemplateGenerator(generator.generator) # Check that parameterizing a molecule fails molecule = self.molecules[0] from simtk.openmm.app import NoCutoff try: # This should fail with an exception openmm_topology = molecule.to_topology().to_openmm() system = forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff) except ValueError as e: # Exception 'No template found...' is expected assert str(e).startswith('No template found') # Now add the molecule to the generator and ensure parameterization passes generator.add_molecules(molecule) openmm_topology = molecule.to_topology().to_openmm() try: system = forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff) except Exception as e: print(forcefield._atomTypes.keys()) from simtk.openmm.app import PDBFile PDBFile.writeFile(openmm_topology, molecule.conformers[0]) raise e assert system.getNumParticles() == molecule.n_atoms # Add multiple molecules, including repeats generator.add_molecules(self.molecules) # Ensure all molecules can be parameterized for molecule in self.molecules: openmm_topology = molecule.to_topology().to_openmm() system = forcefield.createSystem(openmm_topology, nonbondedMethod=NoCutoff) assert system.getNumParticles() == molecule.n_atoms
class SystemGenerator(object): """ Utility factory to generate OpenMM Systems from Topology objects. Parameters ---------- forcefields_to_use : list of string List of the names of ffxml files that will be used in system creation. forcefield_kwargs : dict of arguments to createSystem, optional Allows specification of various aspects of system creation. use_gaff : bool, optional, default=True If True, will add the GAFF residue template generator. Examples -------- >>> from simtk.openmm import app >>> forcefield_kwargs={ 'nonbondedMethod' : app.NoCutoff, 'implicitSolvent' : None, 'constraints' : None } >>> system_generator = SystemGenerator(['amber99sbildn.xml'], forcefield_kwargs=forcefield_kwargs) >>> from openmmtools.testsystems import AlanineDipeptideVacuum >>> testsystem = AlanineDipeptideVacuum() >>> system = system_generator.createSystem(testsystem.topology) """ def __init__(self, forcefields_to_use, forcefield_kwargs=None, use_gaff=True): self._forcefield_xmls = forcefields_to_use self._forcefield_kwargs = forcefield_kwargs if forcefield_kwargs is not None else {} from simtk.openmm.app import ForceField self._forcefield = ForceField(*self._forcefield_xmls) if use_gaff: self._forcefield.registerTemplateGenerator(gaffTemplateGenerator) def getForceField(self): """ Return the associated ForceField object. Returns ------- forcefield : simtk.openmm.app.ForceField The current ForceField object. """ return self._forcefield def createSystem(self, topology): """ Build a system from specified topology object. Parameters ---------- topology : simtk.openmm.app.Topology object The topology of the system to construct. Returns ------- system : openmm.System A system object generated from the topology """ system = self._forcefield.createSystem(topology, **self._forcefield_kwargs) return system @property def ffxmls(self): return self._forcefield_xmls @property def forcefield(self): return self._forcefield
def test_jacs_complexes(self): """Use template generator to parameterize the Schrodinger JACS set of complexes""" # TODO: Uncomment working systems when we have cleaned up the input files jacs_systems = { #'bace' : { 'prefix' : 'Bace' }, #'cdk2' : { 'prefix' : 'CDK2' }, #'jnk1' : { 'prefix' : 'Jnk1' }, 'mcl1' : { 'prefix' : 'MCL1' }, #'p38' : { 'prefix' : 'p38' }, #'ptp1b' : { 'prefix' : 'PTP1B' }, #'thrombin' : { 'prefix' : 'Thrombin' }, #'tyk2' : { 'prefix' : 'Tyk2' }, } for system_name in jacs_systems: prefix = jacs_systems[system_name]['prefix'] # Read molecules ligand_sdf_filename = get_data_filename(os.path.join('perses_jacs_systems', system_name, prefix + '_ligands.sdf')) print(f'Reading molecules from {ligand_sdf_filename} ...') from openforcefield.topology import Molecule molecules = Molecule.from_file(ligand_sdf_filename, allow_undefined_stereo=True) try: nmolecules = len(molecules) except TypeError: molecules = [molecules] print(f'Read {len(molecules)} molecules from {ligand_sdf_filename}') # Read ParmEd Structures import parmed from simtk import unit protein_pdb_filename = get_data_filename(os.path.join('perses_jacs_systems', system_name, prefix + '_protein.pdb')) from simtk.openmm.app import PDBFile print(f'Reading protein from {protein_pdb_filename} ...') #protein_structure = parmed.load_file(protein_pdb_filename) # NOTE: This mis-interprets distorted geometry and sequentially-numbered residues that span chain breaks pdbfile = PDBFile(protein_pdb_filename) protein_structure = parmed.openmm.load_topology(pdbfile.topology, xyz=pdbfile.positions.value_in_unit(unit.angstroms)) ligand_structures = parmed.load_file(ligand_sdf_filename) try: nmolecules = len(ligand_structures) except TypeError: ligand_structures = [ligand_structures] assert len(ligand_structures) == len(molecules) # Filter molecules if 'TRAVIS' in os.environ: MAX_MOLECULES = 3 else: MAX_MOLECULES = 6 molecules = molecules[:MAX_MOLECULES] ligand_structures = ligand_structures[:MAX_MOLECULES] print(f'{len(molecules)} molecules remain after filtering') # Create complexes complex_structures = [ (protein_structure + ligand_structure) for ligand_structure in ligand_structures ] # Create template generator with local cache cache = os.path.join(get_data_filename(os.path.join('perses_jacs_systems', system_name)), 'cache.json') generator = self.TEMPLATE_GENERATOR(molecules=molecules, cache=cache) # Create a ForceField from simtk.openmm.app import ForceField forcefield = ForceField(*self.amber_forcefields) # Register the template generator forcefield.registerTemplateGenerator(generator.generator) # Parameterize all complexes print(f'Caching all molecules for {system_name} at {cache} ...') for ligand_index, complex_structure in enumerate(complex_structures): openmm_topology = complex_structure.topology molecule = molecules[ligand_index] # Delete hydrogens from terminal protein residues # TODO: Fix the input files so we don't need to do this from simtk.openmm import app modeller = app.Modeller(complex_structure.topology, complex_structure.positions) residues = [residue for residue in modeller.topology.residues() if residue.name != 'UNL'] termini_ids = [residues[0].id, residues[-1].id] #hs = [atom for atom in modeller.topology.atoms() if atom.element.symbol in ['H'] and atom.residue.name != 'UNL'] hs = [atom for atom in modeller.topology.atoms() if atom.element.symbol in ['H'] and atom.residue.id in termini_ids] modeller.delete(hs) from simtk.openmm.app import PDBFile modeller.addHydrogens(forcefield) # Parameterize protein:ligand complex in vacuum print(f' Parameterizing {system_name} : {molecule.to_smiles()} in vacuum...') from simtk.openmm.app import NoCutoff forcefield.createSystem(modeller.topology, nonbondedMethod=NoCutoff) # Parameterize protein:ligand complex in solvent print(f' Parameterizing {system_name} : {molecule.to_smiles()} in explicit solvent...') from simtk.openmm.app import PME modeller.addSolvent(forcefield, padding=0*unit.angstroms, ionicStrength=300*unit.millimolar) forcefield.createSystem(modeller.topology, nonbondedMethod=PME)