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 __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 test_generate_ffxml_from_molecules(): """ Test generation of single ffxml file from a list of molecules """ # Create a test set of molecules. molecules = [createOEMolFromIUPAC(name) for name in IUPAC_molecule_names] # Create an ffxml file. from openmoltools.forcefield_generators import generateForceFieldFromMolecules ffxml = generateForceFieldFromMolecules(molecules) # Create a ForceField. gaff_xml_filename = utils.get_data_filename("parameters/gaff.xml") forcefield = ForceField(gaff_xml_filename) try: forcefield.loadFile(StringIO(ffxml)) except Exception as e: msg = str(e) msg += "ffxml contents:\n" for (index, line) in enumerate(ffxml.split("\n")): msg += "line %8d : %s\n" % (index, line) raise Exception(msg) # Parameterize the molecules. from openmoltools.forcefield_generators import generateTopologyFromOEMol for molecule in molecules: # Create topology from molecule. topology = generateTopologyFromOEMol(molecule) # Create system with forcefield. system = forcefield.createSystem(topology) # Check potential is finite. positions = extractPositionsFromOEMOL(molecule) check_potential_is_finite(system, positions)
def test_porin_membrane_system(): """Test the addition of a ligand to a solvated porin""" # pdb file corresponding to a solvated porin pdb = PDBFile( os.path.join(os.path.dirname(__file__), '../data/porin/solvated-porin.pdb')) modeller = app.Modeller(pdb.topology, pdb.positions) forcefield = ForceField('amber14-all.xml', 'amber14/tip3pfb.xml') platform = mm.Platform.getPlatformByName('CPU') modeller.addHydrogens(forcefield=forcefield) # rigidWater False is required for ParMed to access water paramters system_md = forcefield.createSystem(modeller.topology, nonbondedMethod=app.PME, rigidWater=False, nonbondedCutoff=1 * unit.nanometer) ligand_system = PorinMembraneSystem('comp7', system_md, modeller.topology, modeller.positions, platform, tolerance=1 * unit.kilojoule / unit.mole, max_iterations=200) integrator = mm.LangevinIntegrator(300 * unit.kelvin, 1.0 / unit.picoseconds, 2 * unit.femtosecond) simulation = app.Simulation(ligand_system.structure.topology, ligand_system.system, integrator, platform) simulation.context.setPositions(ligand_system.structure.positions) state = simulation.context.getState(getEnergy=True) pe = state.getPotentialEnergy()._value assert pe < 0.0
def test_generate_ffxml_from_molecules(): """ Test generation of single ffxml file from a list of molecules """ # Create a test set of molecules. molecules = [createOEMolFromIUPAC(name) for name in IUPAC_molecule_names] # Create an ffxml file. from openmoltools.forcefield_generators import generateForceFieldFromMolecules ffxml = generateForceFieldFromMolecules(molecules) # Create a ForceField. gaff_xml_filename = utils.get_data_filename("parameters/gaff.xml") forcefield = ForceField(gaff_xml_filename) try: forcefield.loadFile(StringIO(ffxml)) except Exception as e: msg = str(e) msg += "ffxml contents:\n" for (index, line) in enumerate(ffxml.split('\n')): msg += 'line %8d : %s\n' % (index, line) raise Exception(msg) # Parameterize the molecules. from openmoltools.forcefield_generators import generateTopologyFromOEMol for molecule in molecules: # Create topology from molecule. topology = generateTopologyFromOEMol(molecule) # Create system with forcefield. system = forcefield.createSystem(topology) # Check potential is finite. positions = extractPositionsFromOEMOL(molecule) check_potential_is_finite(system, positions)
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 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_membraneModeller(): # pdb file containing a solvated single lipid molecule pdb = PDBFile('solvated-lipid.pdb') modeller = Modeller(pdb.topology,pdb.positions) modeller.modifyTopology() forcefield = ForceField('amber14-all.xml', 'amber14/tip3pfb.xml') modeller.addHydrogens(forcefield=forcefield) system = forcefield.createSystem(modeller.topology, nonbondedMethod=app.PME, rigidWater=True, nonbondedCutoff=1*unit.nanometer) integrator = mm.VerletIntegrator(0.5*unit.femtoseconds) platform = mm.Platform.getPlatformByName('Reference') simulation = app.Simulation(modeller.topology, system, integrator, platform) simulation.context.setPositions(modeller.positions) simulation.context.setVelocitiesToTemperature(300*unit.kelvin) # Minimize the system after adding hydrogens tolerance = 0.1*unit.kilojoules_per_mole/unit.angstroms simulation.minimizeEnergy(tolerance=tolerance,maxIterations=200) simulation.reporters.append(app.StateDataReporter('relax-hydrogens.log', 1000, step=True, potentialEnergy=True)) simulation.step(1000)
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_membrane_modeller(): """Test the addition of hydrogens to a solvated DPPC molecule""" # pdb file corresponding to a solvated lipid molecule pdb = PDBFile(os.path.join(os.path.dirname(__file__), '../data/dppc/solvated-dppc.pdb')) modeller = MembraneModeller(pdb.topology,pdb.positions) modeller.modify_topology() forcefield = ForceField('amber14-all.xml', 'amber14/tip3pfb.xml') modeller.addHydrogens(forcefield=forcefield) system = forcefield.createSystem(modeller.topology, nonbondedMethod=app.PME, rigidWater=True, nonbondedCutoff=1*unit.nanometer) integrator = mm.VerletIntegrator(0.5*unit.femtoseconds) platform = mm.Platform.getPlatformByName('Reference') simulation = app.Simulation(modeller.topology, system, integrator, platform) simulation.context.setPositions(modeller.positions) simulation.context.setVelocitiesToTemperature(300*unit.kelvin) # Minimize the system after adding hydrogens simulation.minimizeEnergy(maxIterations=200) # Run a few MD steps to check the system has no overlaps simulation.step(1000) state = simulation.context.getState(getEnergy=True) pe = state.getPotentialEnergy()._value assert pe < 0.0
def _parse_force_field(self, *args): if args[0] == 'TraPPE-UA': self.forceField = ForceField(self.TRAPPEUA_FF_PATH) elif args[0] == 'OPLS-AA': self.forceField = ForceField(self.OPLS_AA_PATH) else: raise ValueError("Invalid force field.") self.forceField_str = args[0]
def __init__(self, system_options): super(DodecaneAcrylateTopologyOptions, self).__init__(system_options) self.forceField_str = "TraPPE-UA" self.forceField = ForceField(self.TRAPPEUA_FF_PATH) self.numDodecane = 0 self.numSqualane = 0 self.dodecaneInstructions = None self.box_vectors = None self.chains = [] self.branched_chains = [] self.id_to_sequence = {}
def test_xtc_reporter_append(tmpdir, get_fn): pdb = PDBFile(get_fn('native.pdb')) forcefield = ForceField('amber99sbildn.xml', 'amber99_obc.xml') # NO PERIODIC BOUNDARY CONDITIONS system = forcefield.createSystem(pdb.topology, nonbondedMethod=CutoffNonPeriodic, nonbondedCutoff=1.0 * nanometers, constraints=HBonds, rigidWater=True) integrator = LangevinIntegrator(300 * kelvin, 1.0 / picoseconds, 2.0 * femtoseconds) integrator.setConstraintTolerance(0.00001) platform = Platform.getPlatformByName('Reference') simulation = Simulation(pdb.topology, system, integrator, platform) simulation.context.setPositions(pdb.positions) simulation.context.setVelocitiesToTemperature(300 * kelvin) tmpdir = str(tmpdir) xtcfile = os.path.join(tmpdir, 'traj.xtc') xtcfile_cp = os.path.join(tmpdir, 'traj_cp.xtc') checkpoint = os.path.join(tmpdir, 'checkpoint.chk') reporter = XTCReporter(xtcfile, 2) simulation.reporters.append(reporter) simulation.reporters.append(CheckpointReporter(checkpoint, 10)) simulation.step(10) reporter.close() shutil.copyfile(xtcfile, xtcfile_cp) system = forcefield.createSystem(pdb.topology, nonbondedMethod=CutoffNonPeriodic, nonbondedCutoff=1.0 * nanometers, constraints=HBonds, rigidWater=True) integrator = LangevinIntegrator(300 * kelvin, 1.0 / picoseconds, 2.0 * femtoseconds) integrator.setConstraintTolerance(0.00001) platform = Platform.getPlatformByName('Reference') simulation = Simulation(pdb.topology, system, integrator, platform) simulation.loadCheckpoint(checkpoint) reporter = XTCReporter(xtcfile, 2, append=True) simulation.reporters.append(reporter) simulation.step(10) reporter.close() xtc_traj = md.load(xtcfile, top=get_fn('native.pdb')) xtc_traj_cp = md.load(xtcfile_cp, top=get_fn('native.pdb')) eq(xtc_traj.xyz[:5], xtc_traj_cp.xyz) eq(xtc_traj.n_frames, 10) eq(xtc_traj_cp.n_frames, 5) eq(xtc_traj.time[:5], xtc_traj_cp.time)
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 calculate_fragment_energetics(frag_no=1): """ * Create an OpenMM system with a fragment. * Calculate the energy of the system and print. :param frag_no: The number of the fragment being analysed (used to access files). """ os.chdir(f'group2/frag{frag_no}') # Necessary due to size of calculation sys.setrecursionlimit(15000) pdb = PDBFile(f'QUBE_pro_frag{frag_no}.pdb') forcefield = ForceField(f'QUBE_pro_frag{frag_no}_plus.xml') system = forcefield.createSystem( pdb.topology, nonbondedMethod=NoCutoff, ) system = apply_opls_combo(system) with open(f'QUBE_pro_frag{frag_no}_out.xml', 'w') as outfile: serialized_system = XmlSerializer.serialize(system) outfile.write(serialized_system) # Create the integrator to do Langevin dynamics integrator = LangevinIntegrator( 298.15 * unit.kelvin, # Temperature of heat bath 1.0 / unit.picoseconds, # Friction coefficient 2.0 * unit.femtoseconds, # Time step ) platform = Platform.getPlatformByName('CPU') simulation = Simulation(pdb.topology, system, integrator, platform) simulation.context.setPositions(pdb.positions) print('energy from openmm library') print(simulation.context.getState(getEnergy=True).getPotentialEnergy()) structure = parmed.load_file(f'QUBE_pro_frag{frag_no}.pdb') energy_comps = parmed.openmm.energy_decomposition_system(structure, system) total_energy = 0.0 for comp in energy_comps: total_energy += comp[1] print(*comp) print(f'Total energy {total_energy: 6.6f}')
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 setup(args: ListOfArgs) -> Tuple[PDBFile, int, Simulation]: print("Initialization...") if args.SIM_RANDOM_SEED == 0: random_seed = np.random.randint(2147483647) else: random_seed = args.SIM_RANDOM_SEED print(f" Loading initial structure: {args.INITIAL_STRUCTURE_PATH}") pdb = PDBFile(args.INITIAL_STRUCTURE_PATH) print(f" Loading forcefield file: {args.FORCEFIELD_PATH}") forcefield = ForceField(args.FORCEFIELD_PATH) print(" Building system...") system = forcefield.createSystem(pdb.topology) add_forces_to_system(system, args) integrator = get_integrator(random_seed, args) print(" Setting up simulation...") simulation = Simulation(pdb.topology, system, integrator) simulation.context.setPositions(pdb.positions) return pdb, random_seed, simulation
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 __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 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 add_hydrogens_by_openmm(self): from simtk.openmm.app import ForceField, Modeller, PDBFile from pdbfixer import PDBFixer fixer = PDBFixer(self.name) field = ForceField('amber99sb.xml', 'tip3p.xml') fixer.findMissingResidues() fixer.findMissingAtoms() fixer.addMissingAtoms() fixer.addMissingHydrogens(7.0) modeller = Modeller(fixer.topology, fixer.positions) modeller.addHydrogens(forcefield=field) modeller.deleteWater() PDBFile.writeModel(modeller.topology, modeller.positions, open(self.shotname+'_h.pdb', 'w'))
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
def createSystemFromIUPAC(iupac_name): """ Create an openmm system out of an oemol Parameters ---------- iupac_name : str IUPAC name Returns ------- molecule : openeye.OEMol OEMol molecule system : openmm.System object OpenMM system positions : [n,3] np.array of floats Positions topology : openmm.app.Topology object Topology """ from perses.utils.data import get_data_filename from perses.utils.openeye import extractPositionsFromOEMol # Create OEMol molecule = iupac_to_oemol(iupac_name) # Generate a topology. from openmoltools.forcefield_generators import generateTopologyFromOEMol topology = generateTopologyFromOEMol(molecule) # Initialize a forcefield with GAFF. # TODO: Fix path for `gaff.xml` since it is not yet distributed with OpenMM from simtk.openmm.app import ForceField gaff_xml_filename = get_data_filename('data/gaff.xml') forcefield = ForceField(gaff_xml_filename) # Generate template and parameters. from openmoltools.forcefield_generators import generateResidueTemplate [template, ffxml] = generateResidueTemplate(molecule) # Register the template. forcefield.registerResidueTemplate(template) # Add the parameters. forcefield.loadFile(StringIO(ffxml)) # Create the system. system = forcefield.createSystem(topology, removeCMMotion=False) # Extract positions positions = extractPositionsFromOEMol(molecule) return (molecule, system, positions, topology)
def from_pickle(cls, path, positions=None, forcefield=None, **kwargs): if positions is None: raise ValueError( 'Pickled topology files require initial positions.') if forcefield is None: raise ValueError( 'Pickled topology files require XML/FRCMOD forcefields.') topology = cls._pickle_load(path) forcefield = ForceField(*list(process_forcefield(*forcefield))) return cls(master=forcefield, topology=topology, positions=positions, path=path, **kwargs)
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 createSystemFromIUPAC(iupac_name): """ Create an openmm system out of an oemol Parameters ---------- iupac_name : str IUPAC name Returns ------- molecule : openeye.OEMol OEMol molecule system : openmm.System object OpenMM system positions : [n,3] np.array of floats Positions topology : openmm.app.Topology object Topology """ # Create OEMol molecule = createOEMolFromIUPAC(iupac_name) # Generate a topology. from openmoltools.forcefield_generators import generateTopologyFromOEMol topology = generateTopologyFromOEMol(molecule) # Initialize a forcefield with GAFF. # TODO: Fix path for `gaff.xml` since it is not yet distributed with OpenMM from simtk.openmm.app import ForceField gaff_xml_filename = get_data_filename('data/gaff.xml') forcefield = ForceField(gaff_xml_filename) # Generate template and parameters. from openmoltools.forcefield_generators import generateResidueTemplate [template, ffxml] = generateResidueTemplate(molecule) # Register the template. forcefield.registerResidueTemplate(template) # Add the parameters. forcefield.loadFile(StringIO(ffxml)) # Create the system. system = forcefield.createSystem(topology) # Extract positions positions = extractPositionsFromOEMOL(molecule) return (molecule, system, positions, topology)
def check_hydrogens(molecule, ID): # Check that Hydrogens are in structure if len(molecule.top.select("name == H")) == 0: # If absent, then add Hydrogens using the Amber99sb force-field try: from simtk.openmm.app import PDBFile, Modeller, ForceField pdb = PDBFile(ID + ".pdb") modeller = Modeller(pdb.topology, pdb.positions) forcefield = ForceField('amber99sb.xml', 'tip3p.xml') modeller.addHydrogens(forcefield) PDBFile.writeFile(modeller.topology, modeller.positions, open(ID + ".pdb", 'w')) molecule = md.load(ID + ".pdb").remove_solvent() except: warnings.warn( """PDB topology missing Hydrogens. Either manually add or install OpenMM through SIMTK to automatically correct.""") pass return molecule
def from_pdb(cls, path, forcefield=None, loader=PDBFile, strict=True, **kwargs): """ Loads topology, positions and, potentially, velocities and vectors, from a PDB or PDBx file Parameters ---------- path : str Path to PDB/PDBx file forcefields : list of str Paths to FFXML and/or FRCMOD forcefields. REQUIRED. Returns ------- pdb : SystemHandler SystemHandler with topology, positions, and, potentially, velocities and box vectors. Forcefields are embedded in the `master` attribute. """ pdb = loader(path) box = kwargs.pop('box', pdb.topology.getPeriodicBoxVectors()) positions = kwargs.pop('positions', pdb.positions) velocities = kwargs.pop('velocities', getattr(pdb, 'velocities', None)) if strict and not forcefield: from .md import FORCEFIELDS as forcefield logger.info( '! Forcefields for PDB not specified. Using default: %s', ', '.join(forcefield)) pdb.forcefield = ForceField(*list(process_forcefield(*forcefield))) return cls(master=pdb.forcefield, topology=pdb.topology, positions=positions, velocities=velocities, box=box, path=path, **kwargs)
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
def _assignNamesFromForceFieldTemplates(topology, system, openmm_forcefields_to_use): ''' Assign Amber atom and residue names from specified forcefield templates. Requires OpenMM version >= 5.2. ARGUMENTS topology system openmm_forcefields_to_use (list of strings) - list of XML files of forcefields containing templates RETURNS atoms - list of atom information sorted_atom_indices (list of int) - atom indices in order that they appear in forcefield templates, in case re-sorting is required ''' # Assign AMBER atom and residue names. from simtk.openmm.app import ForceField import simtk.openmm.app.forcefield forcefield = ForceField(*openmm_forcefields_to_use) data = ForceField._SystemData() atomIndices = {} for index, atom in enumerate(topology.atoms()): data.atoms.append(atom) atomIndices[atom] = index # Make a list of all bonds for bond in topology.bonds(): if bond[0] in atomIndices and bond[1] in atomIndices: data.bonds.append(ForceField._BondData(atomIndices[bond[0]], atomIndices[bond[1]])) # Record which atoms are bonded to each other atom bondedToAtom = [] for i in range(len(data.atoms)): bondedToAtom.append(set()) data.atomBonds.append([]) for i in range(len(data.bonds)): bond = data.bonds[i] bondedToAtom[bond.atom1].add(bond.atom2) bondedToAtom[bond.atom2].add(bond.atom1) data.atomBonds[bond.atom1].append(i) data.atomBonds[bond.atom2].append(i) # Find the template matching each residue and assign atom types. sorted_atom_indices = list() for chain in topology.chains(): for res in chain.residues(): template = None matches = None # NOTE: Before OpenMM 5.2, was necessary to convert output of _createResidueSignature using the method simtk.openmm.app.forcefield._signatureToString - this is now done within _createResidueSignature sig = simtk.openmm.app.forcefield._createResidueSignature([atom.element for atom in res.atoms()]) if sig != '': if sig in forcefield._templateSignatures: for t in forcefield._templateSignatures[sig]: # NOTE: No longer necessary to pass atomIndices in OpenMM > 5.2 matches = simtk.openmm.app.forcefield._matchResidue(res, t, bondedToAtom) if matches is not None: template = t break if matches is None: # Check templates involving virtual sites for t in forcefield._templateSignatures[None]: matches = simtk.openmm.app.forcefield._matchResidue(res, t, bondedToAtom) if matches is not None: template = t break if matches is None: raise ValueError('No template found for residue %d (%s). This might mean your input topology is missing some atoms or bonds, or possibly that you are using the wrong force field.' % (res.index+1, res.name)) # Sort matches by order in template. atom_indices = [ atom.index for atom in res.atoms() ] for local_index in range(len(template.atoms)): if local_index in matches: sorted_atom_indices.append(min(atom_indices) + matches.index(local_index)) for (atom, match) in zip(res.atoms(), matches): data.atomType[atom] = template.atoms[match].type # Rename atom (JDC). atom.name = template.atoms[match].name atom.resname = template.name for site in template.virtualSites: if match == site.index: data.virtualSites[atom] = site return (data.atoms, sorted_atom_indices)
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)
def generateResidueTemplate(molecule, residue_atoms=None, normalize=True, gaff_version='gaff'): """ Generate an residue template for simtk.openmm.app.ForceField using GAFF/AM1-BCC. This requires the OpenEye toolkit. Parameters ---------- molecule : openeye.oechem.OEMol The molecule to be parameterized. The molecule must have explicit hydrogens. Net charge will be inferred from the net formal charge on each molecule. Partial charges will be determined automatically using oequacpac and canonical AM1-BCC charging rules. residue_atomset : set of OEAtom, optional, default=None If not None, only the atoms in this set will be used to construct the residue template normalize : bool, optional, default=True If True, normalize the molecule by checking aromaticity, adding explicit hydrogens, and renaming by IUPAC name. gaff_version : str, default = 'gaff' One of ['gaff', 'gaff2']; selects which atom types to use. Returns ------- template : simtk.openmm.app.forcefield._TemplateData Residue template for ForceField using atom types and parameters from `gaff.xml` or `gaff2.xml`. additional_parameters_ffxml : str Contents of ForceField `ffxml` file defining additional parameters from parmchk(2). Notes ----- The residue template will be named after the molecule title. This method preserves stereochemistry during AM1-BCC charge parameterization. Atom names in molecules will be assigned Tripos atom names if any are blank or not unique. """ # Set the template name based on the molecule title plus a globally unique UUID. from uuid import uuid4 template_name = molecule.GetTitle() + '-' + str(uuid4()) # If any atom names are not unique, atom names _ensureUniqueAtomNames(molecule) # Compute net formal charge. net_charge = _computeNetCharge(molecule) # Generate canonical AM1-BCC charges and a reference conformation. molecule = get_charges(molecule, strictStereo=False, keep_confs=1, normalize=normalize) # DEBUG: This may be necessary. molecule.SetTitle('MOL') # Create temporary directory for running antechamber. import tempfile tmpdir = tempfile.mkdtemp() prefix = 'molecule' input_mol2_filename = os.path.join(tmpdir, prefix + '.tripos.mol2') gaff_mol2_filename = os.path.join(tmpdir, prefix + '.gaff.mol2') frcmod_filename = os.path.join(tmpdir, prefix + '.frcmod') # Write Tripos mol2 file as antechamber input. _writeMolecule(molecule, input_mol2_filename, standardize=normalize) # Parameterize the molecule with antechamber. run_antechamber(template_name, input_mol2_filename, charge_method=None, net_charge=net_charge, gaff_mol2_filename=gaff_mol2_filename, frcmod_filename=frcmod_filename, gaff_version=gaff_version) # Read the resulting GAFF mol2 file as a ParmEd structure. from openeye import oechem ifs = oechem.oemolistream(gaff_mol2_filename) ifs.SetFlavor( oechem.OEFormat_MOL2, oechem.OEIFlavor_MOL2_DEFAULT | oechem.OEIFlavor_MOL2_M2H | oechem.OEIFlavor_MOL2_Forcefield) m2h = True oechem.OEReadMolecule(ifs, molecule) ifs.close() # If residue_atoms = None, add all atoms to the residues if residue_atoms == None: residue_atoms = [atom for atom in molecule.GetAtoms()] # Modify partial charges so that charge on residue atoms is integral. residue_charge = 0.0 sum_of_absolute_charge = 0.0 for atom in residue_atoms: charge = atom.GetPartialCharge() residue_charge += charge sum_of_absolute_charge += abs(charge) excess_charge = residue_charge - net_charge if sum_of_absolute_charge == 0.0: sum_of_absolute_charge = 1.0 for atom in residue_atoms: charge = atom.GetPartialCharge() atom.SetPartialCharge(charge + excess_charge * (abs(charge) / sum_of_absolute_charge)) # Create residue template. template = ForceField._TemplateData(template_name) for (index, atom) in enumerate(molecule.GetAtoms()): atomname = atom.GetName() typename = atom.GetType() element = Element.getByAtomicNumber(atom.GetAtomicNum()) charge = atom.GetPartialCharge() parameters = {'charge': charge} atom_template = ForceField._TemplateAtomData(atomname, typename, element, parameters) template.atoms.append(atom_template) for bond in molecule.GetBonds(): if (bond.GetBgn() in residue_atoms) and (bond.GetEnd() in residue_atoms): template.addBondByName(bond.GetBgn().GetName(), bond.GetEnd().GetName()) elif (bond.GetBgn() in residue_atoms) and (bond.GetEnd() not in residue_atoms): template.addExternalBondByName(bond.GetBgn().GetName()) elif (bond.GetBgn() not in residue_atoms) and (bond.GetEnd() in residue_atoms): template.addExternalBondByName(bond.GetEnd().GetName()) # Generate ffxml file contents for parmchk-generated frcmod output. leaprc = StringIO('parm = loadamberparams %s' % frcmod_filename) params = parmed.amber.AmberParameterSet.from_leaprc(leaprc) params = parmed.openmm.OpenMMParameterSet.from_parameterset(params) ffxml = StringIO() params.write(ffxml) return template, ffxml.getvalue()
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 generateResidueTemplate(molecule, residue_atoms=None, normalize=True, gaff_version='gaff'): """ Generate an residue template for simtk.openmm.app.ForceField using GAFF/AM1-BCC. This requires the OpenEye toolkit. Parameters ---------- molecule : openeye.oechem.OEMol The molecule to be parameterized. The molecule must have explicit hydrogens. Net charge will be inferred from the net formal charge on each molecule. Partial charges will be determined automatically using oequacpac and canonical AM1-BCC charging rules. residue_atomset : set of OEAtom, optional, default=None If not None, only the atoms in this set will be used to construct the residue template normalize : bool, optional, default=True If True, normalize the molecule by checking aromaticity, adding explicit hydrogens, and renaming by IUPAC name. gaff_version : str, default = 'gaff' One of ['gaff', 'gaff2']; selects which atom types to use. Returns ------- template : simtk.openmm.app.forcefield._TemplateData Residue template for ForceField using atom types and parameters from `gaff.xml` or `gaff2.xml`. additional_parameters_ffxml : str Contents of ForceField `ffxml` file defining additional parameters from parmchk(2). Notes ----- The residue template will be named after the molecule title. This method preserves stereochemistry during AM1-BCC charge parameterization. Atom names in molecules will be assigned Tripos atom names if any are blank or not unique. """ # Set the template name based on the molecule title plus a globally unique UUID. from uuid import uuid4 template_name = molecule.GetTitle() + '-' + str(uuid4()) # If any atom names are not unique, atom names _ensureUniqueAtomNames(molecule) # Compute net formal charge. net_charge = _computeNetCharge(molecule) # Generate canonical AM1-BCC charges and a reference conformation. molecule = get_charges(molecule, strictStereo=False, keep_confs=1, normalize=normalize) # DEBUG: This may be necessary. molecule.SetTitle('MOL') # Create temporary directory for running antechamber. import tempfile tmpdir = tempfile.mkdtemp() prefix = 'molecule' input_mol2_filename = os.path.join(tmpdir, prefix + '.tripos.mol2') gaff_mol2_filename = os.path.join(tmpdir, prefix + '.gaff.mol2') frcmod_filename = os.path.join(tmpdir, prefix + '.frcmod') # Write Tripos mol2 file as antechamber input. _writeMolecule(molecule, input_mol2_filename, standardize=normalize) # Parameterize the molecule with antechamber. run_antechamber(template_name, input_mol2_filename, charge_method=None, net_charge=net_charge, gaff_mol2_filename=gaff_mol2_filename, frcmod_filename=frcmod_filename, gaff_version=gaff_version) # Read the resulting GAFF mol2 file as a ParmEd structure. from openeye import oechem ifs = oechem.oemolistream(gaff_mol2_filename) ifs.SetFlavor(oechem.OEFormat_MOL2, oechem.OEIFlavor_MOL2_DEFAULT | oechem.OEIFlavor_MOL2_M2H | oechem.OEIFlavor_MOL2_Forcefield) m2h = True oechem.OEReadMolecule(ifs, molecule) ifs.close() # If residue_atoms = None, add all atoms to the residues if residue_atoms == None: residue_atoms = [ atom for atom in molecule.GetAtoms() ] # Modify partial charges so that charge on residue atoms is integral. residue_charge = 0.0 sum_of_absolute_charge = 0.0 for atom in residue_atoms: charge = atom.GetPartialCharge() residue_charge += charge sum_of_absolute_charge += abs(charge) excess_charge = residue_charge - net_charge if sum_of_absolute_charge == 0.0: sum_of_absolute_charge = 1.0 for atom in residue_atoms: charge = atom.GetPartialCharge() atom.SetPartialCharge( charge + excess_charge * (abs(charge) / sum_of_absolute_charge) ) # Create residue template. template = ForceField._TemplateData(template_name) for (index, atom) in enumerate(molecule.GetAtoms()): atomname = atom.GetName() typename = atom.GetType() element = Element.getByAtomicNumber(atom.GetAtomicNum()) charge = atom.GetPartialCharge() parameters = { 'charge' : charge } atom_template = ForceField._TemplateAtomData(atomname, typename, element, parameters) template.atoms.append(atom_template) for bond in molecule.GetBonds(): if (bond.GetBgn() in residue_atoms) and (bond.GetEnd() in residue_atoms): template.addBondByName(bond.GetBgn().GetName(), bond.GetEnd().GetName()) elif (bond.GetBgn() in residue_atoms) and (bond.GetEnd() not in residue_atoms): template.addExternalBondByName(bond.GetBgn().GetName()) elif (bond.GetBgn() not in residue_atoms) and (bond.GetEnd() in residue_atoms): template.addExternalBondByName(bond.GetEnd().GetName()) # Generate ffxml file contents for parmchk-generated frcmod output. leaprc = StringIO('parm = loadamberparams %s' % frcmod_filename) params = parmed.amber.AmberParameterSet.from_leaprc(leaprc) params = parmed.openmm.OpenMMParameterSet.from_parameterset(params) ffxml = StringIO() params.write(ffxml) return template, ffxml.getvalue()
def test_reporter(tmpdir, get_fn): pdb = PDBFile(get_fn('native.pdb')) forcefield = ForceField('amber99sbildn.xml', 'amber99_obc.xml') # NO PERIODIC BOUNDARY CONDITIONS system = forcefield.createSystem(pdb.topology, nonbondedMethod=CutoffNonPeriodic, nonbondedCutoff=1.0 * nanometers, constraints=HBonds, rigidWater=True) integrator = LangevinIntegrator(300 * kelvin, 1.0 / picoseconds, 2.0 * femtoseconds) integrator.setConstraintTolerance(0.00001) platform = Platform.getPlatformByName('Reference') simulation = Simulation(pdb.topology, system, integrator, platform) simulation.context.setPositions(pdb.positions) simulation.context.setVelocitiesToTemperature(300 * kelvin) tmpdir = str(tmpdir) hdf5file = os.path.join(tmpdir, 'traj.h5') ncfile = os.path.join(tmpdir, 'traj.nc') dcdfile = os.path.join(tmpdir, 'traj.dcd') xtcfile = os.path.join(tmpdir, 'traj.xtc') reporter = HDF5Reporter(hdf5file, 2, coordinates=True, time=True, cell=True, potentialEnergy=True, kineticEnergy=True, temperature=True, velocities=True) reporter2 = NetCDFReporter(ncfile, 2, coordinates=True, time=True, cell=True) reporter3 = DCDReporter(dcdfile, 2) reporter4 = XTCReporter(xtcfile, 2) simulation.reporters.append(reporter) simulation.reporters.append(reporter2) simulation.reporters.append(reporter3) simulation.reporters.append(reporter4) simulation.step(100) reporter.close() reporter2.close() reporter3.close() reporter4.close() with HDF5TrajectoryFile(hdf5file) as f: got = f.read() eq(got.temperature.shape, (50,)) eq(got.potentialEnergy.shape, (50,)) eq(got.kineticEnergy.shape, (50,)) eq(got.coordinates.shape, (50, 22, 3)) eq(got.velocities.shape, (50, 22, 3)) eq(got.cell_lengths, None) eq(got.cell_angles, None) eq(got.time, 0.002 * 2 * (1 + np.arange(50))) assert f.topology == md.load(get_fn('native.pdb')).top with NetCDFTrajectoryFile(ncfile) as f: xyz, time, cell_lengths, cell_angles = f.read() eq(cell_lengths, None) eq(cell_angles, None) eq(time, 0.002 * 2 * (1 + np.arange(50))) hdf5_traj = md.load(hdf5file) dcd_traj = md.load(dcdfile, top=get_fn('native.pdb')) netcdf_traj = md.load(ncfile, top=get_fn('native.pdb')) xtc_traj = md.load(xtcfile, top=get_fn('native.pdb')) # we don't have to convert units here, because md.load already # handles that assert hdf5_traj.unitcell_vectors is None eq(hdf5_traj.xyz, netcdf_traj.xyz) eq(hdf5_traj.unitcell_vectors, netcdf_traj.unitcell_vectors) eq(hdf5_traj.time, netcdf_traj.time) eq(xtc_traj.time, netcdf_traj.time) eq(dcd_traj.xyz, hdf5_traj.xyz) eq(xtc_traj.xyz, dcd_traj.xyz, decimal=3)
def test_reporter_subset(tmpdir, get_fn): pdb = PDBFile(get_fn('native2.pdb')) pdb.topology.setUnitCellDimensions([2, 2, 2]) forcefield = ForceField('amber99sbildn.xml', 'amber99_obc.xml') system = forcefield.createSystem(pdb.topology, nonbondedMethod=CutoffPeriodic, nonbondedCutoff=1 * nanometers, constraints=HBonds, rigidWater=True) integrator = LangevinIntegrator(300 * kelvin, 1.0 / picoseconds, 2.0 * femtoseconds) integrator.setConstraintTolerance(0.00001) platform = Platform.getPlatformByName('Reference') simulation = Simulation(pdb.topology, system, integrator, platform) simulation.context.setPositions(pdb.positions) simulation.context.setVelocitiesToTemperature(300 * kelvin) tmpdir = str(tmpdir) hdf5file = os.path.join(tmpdir, 'traj.h5') ncfile = os.path.join(tmpdir, 'traj.nc') dcdfile = os.path.join(tmpdir, 'traj.dcd') xtcfile = os.path.join(tmpdir, 'traj.xtc') atomSubset = [0, 1, 2, 4, 5] reporter = HDF5Reporter(hdf5file, 2, coordinates=True, time=True, cell=True, potentialEnergy=True, kineticEnergy=True, temperature=True, velocities=True, atomSubset=atomSubset) reporter2 = NetCDFReporter(ncfile, 2, coordinates=True, time=True, cell=True, atomSubset=atomSubset) reporter3 = DCDReporter(dcdfile, 2, atomSubset=atomSubset) reporter4 = XTCReporter(xtcfile, 2, atomSubset=atomSubset) simulation.reporters.append(reporter) simulation.reporters.append(reporter2) simulation.reporters.append(reporter3) simulation.reporters.append(reporter4) simulation.step(100) reporter.close() reporter2.close() reporter3.close() reporter4.close() t = md.load(get_fn('native.pdb')) t.restrict_atoms(atomSubset) with HDF5TrajectoryFile(hdf5file) as f: got = f.read() eq(got.temperature.shape, (50,)) eq(got.potentialEnergy.shape, (50,)) eq(got.kineticEnergy.shape, (50,)) eq(got.coordinates.shape, (50, len(atomSubset), 3)) eq(got.velocities.shape, (50, len(atomSubset), 3)) eq(got.cell_lengths, 2 * np.ones((50, 3))) eq(got.cell_angles, 90 * np.ones((50, 3))) eq(got.time, 0.002 * 2 * (1 + np.arange(50))) assert f.topology == md.load(get_fn('native.pdb'), atom_indices=atomSubset).topology with NetCDFTrajectoryFile(ncfile) as f: xyz, time, cell_lengths, cell_angles = f.read() eq(cell_lengths, 20 * np.ones((50, 3))) eq(cell_angles, 90 * np.ones((50, 3))) eq(time, 0.002 * 2 * (1 + np.arange(50))) eq(xyz.shape, (50, len(atomSubset), 3)) hdf5_traj = md.load(hdf5file) dcd_traj = md.load(dcdfile, top=hdf5_traj) netcdf_traj = md.load(ncfile, top=hdf5_traj) xtc_traj = md.load(xtcfile, top=hdf5_traj) # we don't have to convert units here, because md.load already handles that eq(hdf5_traj.xyz, netcdf_traj.xyz) eq(hdf5_traj.unitcell_vectors, netcdf_traj.unitcell_vectors) eq(hdf5_traj.time, netcdf_traj.time) eq(xtc_traj.time, netcdf_traj.time) eq(dcd_traj.xyz, hdf5_traj.xyz) eq(xtc_traj.xyz, hdf5_traj.xyz) eq(dcd_traj.unitcell_vectors, hdf5_traj.unitcell_vectors)
def test_generateResidueTemplate(): """ Test GAFF residue template generation from OEMol molecules. """ from openeye import oechem, oeiupac from pkg_resources import resource_filename gaff_xml_filename = utils.get_data_filename("parameters/gaff.xml") # Test independent ForceField instances. for molecule_name in IUPAC_molecule_names: mol = createOEMolFromIUPAC(molecule_name) # Generate an ffxml residue template. from openmoltools.forcefield_generators import generateResidueTemplate [template, ffxml] = generateResidueTemplate(mol) # Create a ForceField object. forcefield = ForceField(gaff_xml_filename) # Add the additional parameters and template to the forcefield. forcefield.registerResidueTemplate(template) forcefield.loadFile(StringIO(ffxml)) # Create a Topology from the molecule. from openmoltools.forcefield_generators import generateTopologyFromOEMol topology = generateTopologyFromOEMol(mol) # Parameterize system. system = forcefield.createSystem(topology, nonbondedMethod=NoCutoff) # Check potential is finite. positions = extractPositionsFromOEMOL(mol) check_potential_is_finite(system, positions) # Test adding multiple molecules to a single ForceField instance. forcefield = ForceField(gaff_xml_filename) for molecule_name in IUPAC_molecule_names: mol = createOEMolFromIUPAC(molecule_name) # Generate an ffxml residue template. from openmoltools.forcefield_generators import generateResidueTemplate [template, ffxml] = generateResidueTemplate(mol) # Add the additional parameters and template to the forcefield. forcefield.registerResidueTemplate(template) forcefield.loadFile(StringIO(ffxml)) # Create a Topology from the molecule. from openmoltools.forcefield_generators import generateTopologyFromOEMol topology = generateTopologyFromOEMol(mol) # Parameterize system. system = forcefield.createSystem(topology, nonbondedMethod=NoCutoff) # Check potential is finite. positions = extractPositionsFromOEMOL(mol) check_potential_is_finite(system, positions)
def generateResidueTemplate(molecule, residue_atoms=None): """ Generate an residue template for simtk.openmm.app.ForceField using GAFF/AM1-BCC. This requires the OpenEye toolkit. Parameters ---------- molecule : openeye.oechem.OEMol The molecule to be parameterized. The molecule must have explicit hydrogens. Charge will be inferred from the net formal charge. residue_atomset : set of OEAtom, optional, default=None If not None, only the atoms in this set will be used to construct the residue template Returns ------- template : simtk.openmm.app.forcefield._TemplateData Residue template for ForceField using atom types and parameters from `gaff.xml`. additional_parameters_ffxml : str Contents of ForceField `ffxml` file defining additional parameters from parmchk(2). Note that this method preserves stereochemistry during AM1-BCC charge parameterization. """ # Generate a unique residue template name to avoid namespace collisions. # TODO: Can we come up with a more intelligent name? #from uuid import uuid4 #template_name = str(uuid4()) template_name = molecule.GetTitle() # Compute net formal charge. from openeye import oechem oechem.OEAssignFormalCharges(molecule) charges = [ atom.GetFormalCharge() for atom in molecule.GetAtoms() ] net_charge = np.array(charges).sum() # Generate canonical AM1-BCC charges and a reference conformation. molecule = get_charges(molecule, strictStereo=False, keep_confs=1) # Create temporary directory for running antechamber. import tempfile tmpdir = tempfile.mkdtemp() input_mol2_filename = os.path.join(tmpdir, template_name + '.tripos.mol2') gaff_mol2_filename = os.path.join(tmpdir, template_name + '.gaff.mol2') frcmod_filename = os.path.join(tmpdir, template_name + '.frcmod') # Write Tripos mol2 file as antechamber input. ofs = oechem.oemolostream(input_mol2_filename) oechem.OEWriteMolecule(ofs, molecule) ofs.close() # Parameterize the molecule with antechamber. run_antechamber(template_name, input_mol2_filename, charge_method=None, net_charge=net_charge, gaff_mol2_filename=gaff_mol2_filename, frcmod_filename=frcmod_filename) # Read the resulting GAFF mol2 file as a ParmEd structure. ifs = oechem.oemolistream(gaff_mol2_filename) ifs.SetFlavor(oechem.OEFormat_MOL2, oechem.OEIFlavor_MOL2_DEFAULT | oechem.OEIFlavor_MOL2_M2H | oechem.OEIFlavor_MOL2_Forcefield) m2h = True oechem.OEReadMolecule(ifs, molecule) ifs.close() # If residue_atoms = None, add all atoms to the residues if residue_atoms == None: residue_atoms = [ atom for atom in molecule.GetAtoms() ] # Modify partial charges so that charge on residue atoms is integral. residue_charge = 0.0 sum_of_absolute_charge = 0.0 for atom in residue_atoms: charge = atom.GetPartialCharge() residue_charge += charge sum_of_absolute_charge += abs(charge) excess_charge = residue_charge - net_charge if sum_of_absolute_charge == 0.0: sum_of_absolute_charge = 1.0 for atom in residue_atoms: charge = atom.GetPartialCharge() atom.SetPartialCharge( charge + excess_charge * (abs(charge) / sum_of_absolute_charge) ) # Create residue template. template = ForceField._TemplateData(template_name) for (index, atom) in enumerate(molecule.GetAtoms()): atomname = atom.GetName() typename = atom.GetType() element = Element.getByAtomicNumber(atom.GetAtomicNum()) charge = atom.GetPartialCharge() parameters = { 'charge' : charge } atom_template = ForceField._TemplateAtomData(atomname, typename, element, parameters) template.atoms.append(atom_template) for bond in molecule.GetBonds(): if (bond.GetBgn() in residue_atoms) and (bond.GetEnd() in residue_atoms): template.addBondByName(bond.GetBgn().GetName(), bond.GetEnd().GetName()) elif (bond.GetBgn() in residue_atoms) and (bond.GetEnd() not in residue_atoms): template.addExternalBondByName(bond.GetBgn().GetName()) elif (bond.GetBgn() not in residue_atoms) and (bond.GetEnd() in residue_atoms): template.addExternalBondByName(bond.GetEnd().GetName()) # Generate ffxml file contents for parmchk-generated frcmod output. leaprc = StringIO("parm = loadamberparams %s" % frcmod_filename) params = parmed.amber.AmberParameterSet.from_leaprc(leaprc) params = parmed.openmm.OpenMMParameterSet.from_parameterset(params) ffxml = StringIO() params.write(ffxml) return template, ffxml.getvalue()
def addExtraParticles(self, forcefield): """Add missing extra particles to the model that are required by a force field. Some force fields use "extra particles" that do not represent actual atoms, but still need to be included in the System. Examples include lone pairs, Drude particles, and the virtual sites used in some water models to adjust the charge distribution. Extra particles can be recognized by the fact that their element is None. This method is primarily used to add extra particles, but it can also remove them. It tries to match every residue in the Topology to a template in the force field. If there is no match, it will both add and remove extra particles as necessary to make it match. Parameters: - forcefield (ForceField) the ForceField defining what extra particles should be present """ # Create copies of all residue templates that have had all extra points removed. templatesNoEP = {} for resName, template in forcefield._templates.iteritems(): if any(atom.element is None for atom in template.atoms): index = 0 newIndex = {} newTemplate = ForceField._TemplateData(resName) for i, atom in enumerate(template.atoms): if atom.element is not None: newIndex[i] = index index += 1 newTemplate.atoms.append(ForceField._TemplateAtomData(atom.name, atom.type, atom.element)) for b1, b2 in template.bonds: if b1 in newIndex and b2 in newIndex: newTemplate.bonds.append((newIndex[b1], newIndex[b2])) newTemplate.atoms[newIndex[b1]].bondedTo.append(newIndex[b2]) newTemplate.atoms[newIndex[b2]].bondedTo.append(newIndex[b1]) for b in template.externalBonds: if b in newIndex: newTemplate.externalBonds.append(newIndex[b]) templatesNoEP[template] = newTemplate # Record which atoms are bonded to each other atom, with and without extra particles. bondedToAtom = [] bondedToAtomNoEP = [] for atom in self.topology.atoms(): bondedToAtom.append(set()) bondedToAtomNoEP.append(set()) for atom1, atom2 in self.topology.bonds(): bondedToAtom[atom1.index].add(atom2.index) bondedToAtom[atom2.index].add(atom1.index) if atom1.element is not None and atom2.element is not None: bondedToAtomNoEP[atom1.index].add(atom2.index) bondedToAtomNoEP[atom2.index].add(atom1.index) # If the force field has a DrudeForce, record the types of Drude particles and their parents since we'll # need them for picking particle positions. drudeTypeMap = {} for force in forcefield._forces: if isinstance(force, DrudeGenerator): for type in force.typeMap: drudeTypeMap[type] = force.typeMap[type][0] # Create the new Topology. newTopology = Topology() newTopology.setUnitCellDimensions(deepcopy(self.topology.getUnitCellDimensions())) newAtoms = {} newPositions = []*nanometer for chain in self.topology.chains(): newChain = newTopology.addChain() for residue in chain.residues(): newResidue = newTopology.addResidue(residue.name, newChain) # Look for a matching template. matchFound = False signature = _createResidueSignature([atom.element for atom in residue.atoms()]) if signature in forcefield._templateSignatures: for t in forcefield._templateSignatures[signature]: if _matchResidue(residue, t, bondedToAtom) is not None: matchFound = True if matchFound: # Just copy the residue over. for atom in residue.atoms(): newAtom = newTopology.addAtom(atom.name, atom.element, newResidue) newAtoms[atom] = newAtom newPositions.append(deepcopy(self.positions[atom.index])) else: # There's no matching template. Try to find one that matches based on everything except # extra points. template = None residueNoEP = Residue(residue.name, residue.index, residue.chain) residueNoEP._atoms = [atom for atom in residue.atoms() if atom.element is not None] if signature in forcefield._templateSignatures: for t in forcefield._templateSignatures[signature]: if t in templatesNoEP: matches = _matchResidue(residueNoEP, templatesNoEP[t], bondedToAtomNoEP) if matches is not None: template = t; # Record the corresponding atoms. matchingAtoms = {} for atom, match in zip(residueNoEP.atoms(), matches): templateAtomName = t.atoms[match].name for templateAtom in template.atoms: if templateAtom.name == templateAtomName: matchingAtoms[templateAtom] = atom break if template is None: raise ValueError('Residue %d (%s) does not match any template defined by the ForceField.' % (residue.index+1, residue.name)) # Add the regular atoms. for atom in residue.atoms(): if atom.element is not None: newAtoms[atom] = newTopology.addAtom(atom.name, atom.element, newResidue) newPositions.append(deepcopy(self.positions[atom.index])) # Add the extra points. templateAtomPositions = len(template.atoms)*[None] for index, atom in enumerate(template.atoms): if atom in matchingAtoms: templateAtomPositions[index] = self.positions[matchingAtoms[atom].index].value_in_unit(nanometer) for index, atom in enumerate(template.atoms): if atom.element is None: newTopology.addAtom(atom.name, None, newResidue) position = None for site in template.virtualSites: if site.index == index: # This is a virtual site. Compute its position by the correct rule. if site.type == 'average2': position = site.weights[0]*templateAtomPositions[index+site.atoms[0]] + site.weights[1]*templateAtomPositions[index+site.atoms[1]] elif site.type == 'average3': position = site.weights[0]*templateAtomPositions[index+site.atoms[0]] + site.weights[1]*templateAtomPositions[index+site.atoms[1]] + site.weights[2]*templateAtomPositions[index+site.atoms[2]] elif site.type == 'outOfPlane': v1 = templateAtomPositions[index+site.atoms[1]] - templateAtomPositions[index+site.atoms[0]] v2 = templateAtomPositions[index+site.atoms[2]] - templateAtomPositions[index+site.atoms[0]] cross = Vec3(v1[1]*v2[2]-v1[2]*v2[1], v1[2]*v2[0]-v1[0]*v2[2], v1[0]*v2[1]-v1[1]*v2[0]) position = templateAtomPositions[index+site.atoms[0]] + site.weights[0]*v1 + site.weights[1]*v2 + site.weights[2]*cross if position is None and atom.type in drudeTypeMap: # This is a Drude particle. Put it on top of its parent atom. for atom2, pos in zip(template.atoms, templateAtomPositions): if atom2.type in drudeTypeMap[atom.type]: position = deepcopy(pos) if position is None: # We couldn't figure out the correct position. As a wild guess, just put it at the center of the residue # and hope that energy minimization will fix it. knownPositions = [x for x in templateAtomPositions if x is not None] position = sum(knownPositions)/len(knownPositions) newPositions.append(position*nanometer) for bond in self.topology.bonds(): if bond[0] in newAtoms and bond[1] in newAtoms: newTopology.addBond(newAtoms[bond[0]], newAtoms[bond[1]]) self.topology = newTopology self.positions = newPositions