diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d0a8f0f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: c + +install: + - echo "install" + - echo "$python" + - tools/ci/apt_install.sh + - $python -V + - tools/ci/py_install.sh + +script: + - echo "script" + - $python -V + - sudo $python setup.py -q install + - nosetests diff --git a/chemicals/drugs/test-drugs.py b/chemicals/drugs/test-drugs.py deleted file mode 100644 index 61571e8..0000000 --- a/chemicals/drugs/test-drugs.py +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env python - -import os -import os.path -import tempfile -import sys - -from openeye.oechem import * - -import simtk.openmm -from simtk.openmm import app -import simtk.unit as units - -from gaff2xml.amber_parser import AmberParser - -import logging -logger = logging.getLogger(__name__) -logging.basicConfig(level=logging.DEBUG, format="LOG: %(message)s") - -def run_antechamber(molecule, charge_method=None): - """ - Run AmberTools antechamber to create GAFF mol2 and frcmod files. - - Parameters - ---------- - molecule : openeye.oechem.OEGraphMol - The molecule to be parameterized. - charge_method : str, optional - If not None, the charge method string will be passed to Antechamber. - - Returns - ------- - gaff_mol2_filename : str - GAFF format mol2 filename produced by antechamber - frcmod_filename : str - Amber frcmod file produced by prmchk - - """ - # Get molecule name. - molecule_name = molecule.GetTitle() - logger.debug(molecule_name) - - # Write molecule as Tripos mol2. - tripos_mol2_filename = molecule_name + '.tripos.mol2' - ofs = oemolostream(tripos_mol2_filename) - ofs.SetFormat(OEFormat_MOL2H) - OEWriteMolecule(ofs, molecule) - ofs.close() - - # Replace <0> substructure names with valid text. - infile = open(tripos_mol2_filename, 'r') - lines = infile.readlines() - infile.close() - newlines = [ line.replace('<0>', 'MOL') for line in lines ] - outfile = open(tripos_mol2_filename, 'w') - outfile.writelines(newlines) - outfile.close() - - # Run Antechamber to generate parameters. - gaff_mol2_filename = molecule_name + '.gaff.mol2' - frcmod_filename = molecule_name + '.frcmod' - cmd = "antechamber -i %s -fi mol2 -o %s -fo mol2 -s 2" % (tripos_mol2_filename, gaff_mol2_filename) - if charge_method: - cmd += ' -c %s' % charge_method - logger.debug(cmd) - output = os.system(cmd) - logger.debug(output) - cmd = "parmchk -i %s -f mol2 -o %s" % (gaff_mol2_filename, frcmod_filename) - logger.debug(cmd) - output = os.system(cmd) - logger.debug(output) - - return (gaff_mol2_filename, frcmod_filename) - -def run_tleap(molecule_name, gaff_mol2_filename, frcmod_filename): - prmtop_filename = molecule_name + '.prmtop' - inpcrd_filename = molecule_name + '.inpcrd' - - # Run tleap to generate prmtop/inpcrd. - tleap_input = """ -source leaprc.ff99SB -source leaprc.gaff -LIG = loadmol2 %(gaff_mol2_filename)s -check LIG -loadamberparams %(frcmod_filename)s -saveamberparm LIG %(prmtop_filename)s %(inpcrd_filename)s -quit - -""" % vars() - leap_input_filename = 'leap.in' - outfile = open(leap_input_filename, 'w') - outfile.writelines(tleap_input) - outfile.close() - cmd = "tleap -f %s " % leap_input_filename - output = os.system(cmd) - logger.debug(output) - - return (prmtop_filename, inpcrd_filename) - -def create_ffxml_simulation(molecule, gaff_mol2_filename, frcmod_filename): - molecule_name = molecule.GetTitle() - - # Generate ffxml file. - amberhome_path = os.environ['AMBERHOME'] - amber_parser = AmberParser() - gaff_dat_filename = os.path.join(amberhome_path, 'dat', 'leap', 'parm', 'gaff.dat') - logger.debug(gaff_dat_filename) - amber_parser.parse_filenames([gaff_dat_filename, gaff_mol2_filename, frcmod_filename]) - ffxml_stream = amber_parser.generate_xml() - ffxml_filename = molecule_name + '.ffxml' - outfile = open(ffxml_filename, 'w') - outfile.write(ffxml_stream.read()) - outfile.close() - - # Read mol2 file. - from gaff2xml.gafftools import Mol2Parser - mol2 = Mol2Parser(gaff_mol2_filename) - [topology, positions] = mol2.to_openmm() - - # Create System object. - forcefield = app.ForceField(ffxml_filename) - system = forcefield.createSystem(topology, nonbondedMethod=app.NoCutoff, constraints=None, implicitSolvent=None) - - # Create integrator. - timestep = 1.0 * units.femtoseconds - integrator = simtk.openmm.VerletIntegrator(timestep) - - # Create simulation. - platform = simtk.openmm.Platform.getPlatformByName("Reference") - simulation = app.Simulation(topology, system, integrator, platform=platform) - simulation.context.setPositions(positions) - - return simulation - -def create_leap_simulation(molecule, gaff_mol2_filename, frcmod_filename): - molecule_name = molecule.GetTitle() - - # Parameterize system with LEaP. - [prmtop_filename, inpcrd_filename] = run_tleap(molecule_name, gaff_mol2_filename, frcmod_filename) - - # Create System object. - prmtop = app.AmberPrmtopFile(prmtop_filename) - topology = prmtop.topology - system = prmtop.createSystem(nonbondedMethod=app.NoCutoff, constraints=None, implicitSolvent=None) - - # Read positions. - inpcrd = app.AmberInpcrdFile(inpcrd_filename) - positions = inpcrd.getPositions() - - # Create integrator. - timestep = 1.0 * units.femtoseconds - integrator = simtk.openmm.VerletIntegrator(timestep) - - platform = simtk.openmm.Platform.getPlatformByName("Reference") - simulation = app.Simulation(topology, system, integrator, platform=platform) - simulation.context.setPositions(positions) - - return simulation - pass - -def test_molecule(molecule, charge_method=None): - - # Create temporary directory. - tmp_path = 'tmp' # DEBUG: Create an actual temporary directory. - if not os.path.exists(tmp_path): os.makedirs(tmp_path) - logger.debug('temporary directory created in %s' % tmp_path) - cwd = os.getcwd() - os.chdir(tmp_path) - - # Generate GAFF parameters. - [gaff_mol2_filename, frcmod_filename] = run_antechamber(molecule) - - # Create simulations. - simulation_ffxml = create_ffxml_simulation(molecule, gaff_mol2_filename, frcmod_filename) - simulation_leap = create_leap_simulation(molecule, gaff_mol2_filename, frcmod_filename) - - # Compare simulations. - from gaff2xml.system_checker import SystemChecker - syscheck = SystemChecker(simulation_ffxml, simulation_leap) - syscheck.check_force_parameters() - - # TODO: Clean up temporary directory. - - # Restore current working directory. - os.chdir(cwd) - - return - -if __name__ == '__main__': - - # Test downloaded drug database. - database_filename = 'Zdd.mol2.gz' # mol2 database source - ifs = oemolistream(database_filename) - for molecule in ifs.GetOEGraphMols(): - # Test molecule. - test_molecule(molecule) - diff --git a/gaff2xml/__init__.py b/gaff2xml/__init__.py index b056216..186d54a 100644 --- a/gaff2xml/__init__.py +++ b/gaff2xml/__init__.py @@ -1,13 +1,12 @@ -#!/usr/local/bin/env python +#!/usr/bin/env python """ -YANK, a package for alchemical free energy calculations using pyOpenMM and OpenMM. +gaff2xml, a utility for using GAFF files in OpenMM COPYRIGHT AND LICENSE @author John D. Chodera -@author Kim M. Branson -@author Randall J. Radmer +@author Kyle A. Beauchamp All code in this repository is released under the GNU General Public License. @@ -25,4 +24,4 @@ """ -from gaff2xml import amber_parser, gafftools, system_checker +from gaff2xml import amber_parser, gafftools, system_checker, utils diff --git a/gaff2xml/amber_parser.py b/gaff2xml/amber_parser.py index 23b7f74..ea6a8dc 100755 --- a/gaff2xml/amber_parser.py +++ b/gaff2xml/amber_parser.py @@ -11,6 +11,7 @@ import logging logger = logging.getLogger(__name__) + def fix(atomClass): if atomClass == 'X': return '' @@ -29,14 +30,17 @@ def fix(atomClass): RESIDUECONNECT = 4 section = OTHER -charge14scale = 1.0/1.2 +charge14scale = 1.0 / 1.2 epsilon14scale = 0.5 -skipResidues = ['CIO', 'IB'] # "Generic" ions defined by Amber, which are identical to other real ions -skipClasses = ['OW', 'HW'] # Skip water atoms, since we define these in separate files +skipResidues = ['CIO', 'IB'] # "Generic" ions defined by Amber, which are identical to other real ions +skipClasses = ['OW', 'HW'] # Skip water atoms, since we define these in separate files + class AmberParser(object): + def __init__(self): + """Create an AmberParser object for converting amber force field files to XML format.""" self.residueAtoms = {} self.residueBonds = {} self.residueConnections = {} @@ -52,10 +56,21 @@ def __init__(self): self.angles = [] self.torsions = [] self.impropers = [] - + self.set_provenance() def addAtom(self, residue, atomName, atomClass, element, charge, use_numeric_types=True): + """Add an atom to the database of FF data. + + Notes + ----- + + use_numeric_types was not originally present in the OpenMM AMBER + parsers. It was added so that we can have atom types of the form + "RES-X", where RES is the name of the molecule or residue and X + is the atom numbering within that molecule. use_numeric_types is + set to False when processing mol2 files--e.g. for ligands. + """ if residue is None: return type_id = len(self.types) @@ -67,25 +82,43 @@ def addAtom(self, residue, atomName, atomClass, element, charge, use_numeric_typ self.type_names.append("%s-%s" % (residue, atomName)) def addBond(self, residue, atom1, atom2): + """Add a bond to the database of FF data.""" if residue is None: return self.residueBonds[residue].append((atom1, atom2)) def addExternalBond(self, residue, atom): + """Add an external bond to the database of FF data.""" if residue is None: return if atom != -1: self.residueConnections[residue] += [atom] def process_mol2_file(self, inputfile): + """Process an AMBER GAFF-compatible mol2 file. + + Parameters + ---------- + inputfile : str + filename of an .mol2 file + + Notes + ----- + + Antechamber is known to produce NONSTANDARD mol2 files. This function + is designed to work with those nonstandard mol2 files, not + Tripos standard mol2 files. We are forced to live with the poor + decisions of our predecessors... + + """ from gaff2xml import gafftools # Late import to delay importing optional modules mol2_parser = gafftools.Mol2Parser(inputfile) residue_name = mol2_parser.atoms.resName[1] # To Do: Add check for consistency - + self.residueAtoms[residue_name] = [] self.residueBonds[residue_name] = [] - self.residueConnections[residue_name] = [] - + self.residueConnections[residue_name] = [] + for (i, name, x, y, z, atype, code, resname, charge) in mol2_parser.atoms.itertuples(False): full_name = residue_name + "_" + name element_symbol = gafftools.gaff_elements[atype] @@ -94,12 +127,19 @@ def process_mol2_file(self, inputfile): self.vdwEquivalents[full_name] = atype for (id0, id1, bond_type) in mol2_parser.bonds.itertuples(False): - i = id0 - 1 # Subtract 1 for zero based indexing in OpenMM??? - j = id1 - 1 # Subtract 1 for zero based indexing in OpenMM??? + i = id0 - 1 # Subtract 1 for zero based indexing in OpenMM??? + j = id1 - 1 # Subtract 1 for zero based indexing in OpenMM??? self.addBond(residue_name, i, j) def process_library_file(self, inputfile): - """Read a library file""" + """Process an AMBER .lib file. + + Parameters + ---------- + inputfile : str + filename of an .lib file + + """ for line in open(inputfile): if line.startswith('!entry'): fields = line.split('.') @@ -132,28 +172,35 @@ def process_library_file(self, inputfile): elif atomClass[0] == 'H': elem = elements[1] else: - raise ValueError('Illegal atomic number: '+line) + raise ValueError('Illegal atomic number: ' + line) else: elem = elements[int(fields[6])] self.charge = float(fields[7]) - addAtom(residue, atomName, atomClass, elem, charge) + self.addAtom(residue, atomName, atomClass, elem, self.charge) elif section == CONNECT: - addExternalBond(residue, int(line)-1) + self.addExternalBond(residue, int(line) - 1) elif section == CONNECTIVITY: fields = line.split() - addBond(residue, int(fields[0])-1, int(fields[1])-1) + self.addBond(residue, int(fields[0]) - 1, int(fields[1]) - 1) elif section == RESIDUECONNECT: # Some Amber files have errors in them, incorrectly listing atoms that should not be # connected in the first two positions. We therefore rely on the "connect" section for # those, using this block only for other external connections. - for atom in [int(x)-1 for x in line.split()[2:]]: - addExternalBond(residue, atom) + for atom in [int(x) - 1 for x in line.split()[2:]]: + self.addExternalBond(residue, atom) def process_dat_file(self, inputfile): - """Read a force field file.""" + """Process an AMBER .dat file. + + Parameters + ---------- + inputfile : str + filename of an .dat file + + """ block = 0 continueTorsion = False - for line in open(inputfile): + for line in open(inputfile): line = line.strip() if block == 0: # Title block += 1 @@ -184,9 +231,9 @@ def process_dat_file(self, inputfile): fields = line[11:].split() periodicity = int(float(fields[3])) if continueTorsion: - self.torsions[-1] += [float(fields[1])/float(fields[0]), fields[2], abs(periodicity)] + self.torsions[-1] += [float(fields[1]) / float(fields[0]), fields[2], abs(periodicity)] else: - self.torsions.append([line[:2].strip(), line[3:5].strip(), line[6:8].strip(), line[9:11].strip(), float(fields[1])/float(fields[0]), fields[2], abs(periodicity)]) + self.torsions.append([line[:2].strip(), line[3:5].strip(), line[6:8].strip(), line[9:11].strip(), float(fields[1]) / float(fields[0]), fields[2], abs(periodicity)]) continueTorsion = (periodicity < 0) elif block == 6: # Improper torsions if len(line) == 0: @@ -208,7 +255,7 @@ def process_dat_file(self, inputfile): block += 1 self.vdwType = line.split()[1] if self.vdwType not in ['RE', 'AC']: - raise ValueError('Nonbonded type (KINDNB) must be RE or AC') + raise ValueError('Nonbonded type (KINDNB) must be RE or AC') elif block == 10: # VDW parameters if len(line) == 0: block += 1 @@ -217,6 +264,14 @@ def process_dat_file(self, inputfile): self.vdw[fields[0]] = (fields[1], fields[2]) def process_frc_file(self, inputfile): + """Process an AMBER .frc file. + + Parameters + ---------- + inputfile : str + filename of an .frc file + + """ block = '' continueTorsion = False first = True @@ -240,9 +295,9 @@ def process_frc_file(self, inputfile): fields = line[11:].split() periodicity = int(float(fields[3])) if continueTorsion: - self.torsions[-1] += [float(fields[1])/float(fields[0]), fields[2], abs(periodicity)] + self.torsions[-1] += [float(fields[1]) / float(fields[0]), fields[2], abs(periodicity)] else: - self.torsions.append([line[:2].strip(), line[3:5].strip(), line[6:8].strip(), line[9:11].strip(), float(fields[1])/float(fields[0]), fields[2], abs(periodicity)]) + self.torsions.append([line[:2].strip(), line[3:5].strip(), line[6:8].strip(), line[9:11].strip(), float(fields[1]) / float(fields[0]), fields[2], abs(periodicity)]) continueTorsion = (periodicity < 0) elif block.startswith('IMPR'): fields = line[11:].split() @@ -252,30 +307,47 @@ def process_frc_file(self, inputfile): self.vdw[fields[0]] = (fields[1], fields[2]) def generate_xml(self): + """Return the processed forcefield files as an XML stream. + + Returns + ------- + stream : cStringIO + The text of the output XML forcefield data. + + Notes + ----- + + The stream can be written to disk via: + + outfile = open("my_forcefield.xml", 'w') + outfile.write(stream.read()) + outfile.close() + + """ stream = cStringIO.StringIO() write_stream = lambda x: stream.write(x + "\n") - write_stream( self.provenance) - write_stream( "") - write_stream( " ") + write_stream(self.provenance) + write_stream("") + write_stream(" ") for index, type in enumerate(self.types): - write_stream( """ """ % (self.type_names[index], type[0], type[1].symbol, type[1].mass.value_in_unit(unit.amu))) - write_stream( " ") - write_stream( " ") + write_stream(""" """ % (self.type_names[index], type[0], type[1].symbol, type[1].mass.value_in_unit(unit.amu))) + write_stream(" ") + write_stream(" ") for res in sorted(self.residueAtoms): - write_stream( """ """ % res) + write_stream(""" """ % res) for atom in self.residueAtoms[res]: atom_name, type_id = tuple(atom) atom_type = self.type_names[type_id] - write_stream( " " % (atom_name, atom_type)) + write_stream(" " % (atom_name, atom_type)) if res in self.residueBonds: for bond in self.residueBonds[res]: - write_stream( """ """ % bond) + write_stream(""" """ % bond) if res in self.residueConnections: for bond in self.residueConnections[res]: - write_stream( """ """ % bond) - write_stream( " ") - write_stream( " ") - write_stream( " ") + write_stream(""" """ % bond) + write_stream(" ") + write_stream(" ") + write_stream(" ") processed = set() for bond in self.bonds: signature = (bond[0], bond[1]) @@ -284,11 +356,11 @@ def generate_xml(self): if any([c in skipClasses for c in signature]): continue processed.add(signature) - length = float(bond[3])*0.1 - k = float(bond[2])*2*100*4.184 - write_stream( """ """ % (bond[0], bond[1], str(length), str(k))) - write_stream( " ") - write_stream( " ") + length = float(bond[3]) * 0.1 + k = float(bond[2]) * 2 * 100 * 4.184 + write_stream(""" """ % (bond[0], bond[1], str(length), str(k))) + write_stream(" ") + write_stream(" ") processed = set() for angle in self.angles: signature = (angle[0], angle[1], angle[2]) @@ -297,11 +369,11 @@ def generate_xml(self): if any([c in skipClasses for c in signature]): continue processed.add(signature) - theta = float(angle[4])*math.pi/180.0 - k = float(angle[3])*2*4.184 - write_stream( """ """ % (angle[0], angle[1], angle[2], str(theta), str(k))) - write_stream( " ") - write_stream( " ") + theta = float(angle[4]) * math.pi / 180.0 + k = float(angle[3]) * 2 * 4.184 + write_stream(""" """ % (angle[0], angle[1], angle[2], str(theta), str(k))) + write_stream(" ") + write_stream(" ") processed = set() for tor in reversed(self.torsions): signature = (fix(tor[0]), fix(tor[1]), fix(tor[2]), fix(tor[3])) @@ -313,14 +385,14 @@ def generate_xml(self): tag = " ") - write_stream( """ """ % (charge14scale, epsilon14scale)) - sigmaScale = 0.1*2.0/(2.0**(1.0/6.0)) + write_stream(tag) + write_stream(" ") + write_stream(""" """ % (charge14scale, epsilon14scale)) + sigmaScale = 0.1 * 2.0 / (2.0 ** (1.0 / 6.0)) for index, type in enumerate(self.types): atomClass = type[0] q = type[2] @@ -351,23 +423,37 @@ def generate_xml(self): if atomClass in self.vdw: params = [float(x) for x in self.vdw[atomClass]] if self.vdwType == 'RE': - sigma = params[0]*sigmaScale - epsilon = params[1]*4.184 + sigma = params[0] * sigmaScale + epsilon = params[1] * 4.184 else: - sigma = (params[0]/params[1])**(1.0/6.0) - epsilon = 4.184*params[1]*params[1]/(4*params[0]) + sigma = (params[0] / params[1]) ** (1.0 / 6.0) + epsilon = 4.184 * params[1] * params[1] / (4 * params[0]) else: sigma = 0 epsilon = 0 if q != 0 or epsilon != 0: - write_stream( """ """ % (self.type_names[index], q, sigma, epsilon)) - write_stream( " ") - write_stream( "") + write_stream(""" """ % (self.type_names[index], q, sigma, epsilon)) + write_stream(" ") + write_stream("") stream.reset() - + return stream def parse_filenames(self, filenames): + """Process a list of filenames according to their filetype suffixes + + Parameters + ---------- + filenames : list (of strings) + List of filenames of type (lib, off, dat, or mol2) + + Notes + ----- + When parameterizing small molecules, the correct order of inputs is: + + $AMBER_LIB_PATH/gaff.dat ligand_name.mol2 ligand_name.frcmod + + """ for inputfile in filenames: if inputfile.endswith('.lib') or inputfile.endswith('.off'): self.process_library_file(inputfile) @@ -377,15 +463,27 @@ def parse_filenames(self, filenames): self.process_mol2_file(inputfile) else: self.process_frc_file(inputfile) - + self.reduce_atomtypes() def reduce_atomtypes(self, symmetrize_protons=False): - """Reduce the list of atom self.types. If symmetrize_protons is True, multiple hydrogens bound to the same heavy atom - should all use the same type. + """Reduce the list of atom self.types. + + Parameters + ---------- + symmetrize_protons : bool, default=False + if True, multiple hydrogens bound to the same heavy atom + should all use the same type. + + Notes + ----- + + The default behavior of symmetrize_protons differs from the + original OpenMM version of this script. For arbitrary small + molecules, we can not assume symmetric protons. """ - removeType = [False]*len(self.types) + removeType = [False] * len(self.types) for res in self.residueAtoms: if res not in self.residueBonds: continue @@ -393,31 +491,35 @@ def reduce_atomtypes(self, symmetrize_protons=False): for bond in self.residueBonds[res]: atomBonds[bond[0]].append(bond[1]) atomBonds[bond[1]].append(bond[0]) - if symmetrize_protons == True: + if symmetrize_protons is True: for index, atom in enumerate(self.residueAtoms[res]): hydrogens = [x for x in atomBonds[index] if self.types[self.residueAtoms[res][x][1]][1] == element.hydrogen] for h in hydrogens[1:]: removeType[self.residueAtoms[res][h][1]] = True self.residueAtoms[res][h][1] = self.residueAtoms[res][hydrogens[0]][1] newTypes = [] - replaceWithType = [0]*len(self.types) + replaceWithType = [0] * len(self.types) for i in range(len(self.types)): if not removeType[i]: newTypes.append(self.types[i]) - replaceWithType[i] = len(newTypes)-1 + replaceWithType[i] = len(newTypes) - 1 self.types = newTypes for res in self.residueAtoms: for atom in self.residueAtoms[res]: atom[1] = replaceWithType[atom[1]] def set_provenance(self): + """Set the provenance attribute with information about the current python session.""" self.provenance = [] line = """\n""" % "Time and parameters of origin:" self.provenance.append(line) now = datetime.datetime.now() line = """\n""" % str(now) self.provenance.append(line) - line = """\n""" % subprocess.list2cmdline(sys.argv[1:]) - self.provenance.append(line) + cmd_string = subprocess.list2cmdline(sys.argv[1:]) + cmd_string = cmd_string.replace("-", " ") # Replace XML specific characters that can break some XML parsers + cmd_string = cmd_string.replace(">", " ") # + cmd_string = cmd_string.replace("<", " ") # + line = """\n""" % cmd_string + self.provenance.append(line) self.provenance = string.join(self.provenance, "") - diff --git a/chemicals/1vii.pdb b/gaff2xml/chemicals/1vii.pdb similarity index 100% rename from chemicals/1vii.pdb rename to gaff2xml/chemicals/1vii.pdb diff --git a/chemicals/benzene/ANTECHAMBER_AC.AC b/gaff2xml/chemicals/benzene/ANTECHAMBER_AC.AC similarity index 100% rename from chemicals/benzene/ANTECHAMBER_AC.AC rename to gaff2xml/chemicals/benzene/ANTECHAMBER_AC.AC diff --git a/chemicals/benzene/ANTECHAMBER_AC.AC0 b/gaff2xml/chemicals/benzene/ANTECHAMBER_AC.AC0 similarity index 100% rename from chemicals/benzene/ANTECHAMBER_AC.AC0 rename to gaff2xml/chemicals/benzene/ANTECHAMBER_AC.AC0 diff --git a/chemicals/benzene/ANTECHAMBER_AM1BCC.AC b/gaff2xml/chemicals/benzene/ANTECHAMBER_AM1BCC.AC similarity index 100% rename from chemicals/benzene/ANTECHAMBER_AM1BCC.AC rename to gaff2xml/chemicals/benzene/ANTECHAMBER_AM1BCC.AC diff --git a/chemicals/benzene/ANTECHAMBER_AM1BCC_PRE.AC b/gaff2xml/chemicals/benzene/ANTECHAMBER_AM1BCC_PRE.AC similarity index 100% rename from chemicals/benzene/ANTECHAMBER_AM1BCC_PRE.AC rename to gaff2xml/chemicals/benzene/ANTECHAMBER_AM1BCC_PRE.AC diff --git a/chemicals/benzene/ANTECHAMBER_BOND_TYPE.AC b/gaff2xml/chemicals/benzene/ANTECHAMBER_BOND_TYPE.AC similarity index 100% rename from chemicals/benzene/ANTECHAMBER_BOND_TYPE.AC rename to gaff2xml/chemicals/benzene/ANTECHAMBER_BOND_TYPE.AC diff --git a/chemicals/benzene/ANTECHAMBER_BOND_TYPE.AC0 b/gaff2xml/chemicals/benzene/ANTECHAMBER_BOND_TYPE.AC0 similarity index 100% rename from chemicals/benzene/ANTECHAMBER_BOND_TYPE.AC0 rename to gaff2xml/chemicals/benzene/ANTECHAMBER_BOND_TYPE.AC0 diff --git a/chemicals/benzene/ATOMTYPE.INF b/gaff2xml/chemicals/benzene/ATOMTYPE.INF similarity index 100% rename from chemicals/benzene/ATOMTYPE.INF rename to gaff2xml/chemicals/benzene/ATOMTYPE.INF diff --git a/chemicals/benzene/benzene.frcmod b/gaff2xml/chemicals/benzene/benzene.frcmod similarity index 100% rename from chemicals/benzene/benzene.frcmod rename to gaff2xml/chemicals/benzene/benzene.frcmod diff --git a/chemicals/benzene/benzene.inpcrd b/gaff2xml/chemicals/benzene/benzene.inpcrd similarity index 100% rename from chemicals/benzene/benzene.inpcrd rename to gaff2xml/chemicals/benzene/benzene.inpcrd diff --git a/chemicals/benzene/benzene.lib b/gaff2xml/chemicals/benzene/benzene.lib similarity index 100% rename from chemicals/benzene/benzene.lib rename to gaff2xml/chemicals/benzene/benzene.lib diff --git a/chemicals/benzene/benzene.mol2 b/gaff2xml/chemicals/benzene/benzene.mol2 similarity index 100% rename from chemicals/benzene/benzene.mol2 rename to gaff2xml/chemicals/benzene/benzene.mol2 diff --git a/chemicals/benzene/benzene.pdb b/gaff2xml/chemicals/benzene/benzene.pdb similarity index 100% rename from chemicals/benzene/benzene.pdb rename to gaff2xml/chemicals/benzene/benzene.pdb diff --git a/chemicals/benzene/benzene.prmtop b/gaff2xml/chemicals/benzene/benzene.prmtop similarity index 100% rename from chemicals/benzene/benzene.prmtop rename to gaff2xml/chemicals/benzene/benzene.prmtop diff --git a/chemicals/benzene/benzene.sdf b/gaff2xml/chemicals/benzene/benzene.sdf similarity index 100% rename from chemicals/benzene/benzene.sdf rename to gaff2xml/chemicals/benzene/benzene.sdf diff --git a/chemicals/benzene/benzene.xml b/gaff2xml/chemicals/benzene/benzene.xml similarity index 100% rename from chemicals/benzene/benzene.xml rename to gaff2xml/chemicals/benzene/benzene.xml diff --git a/chemicals/benzene/leap.log b/gaff2xml/chemicals/benzene/leap.log similarity index 100% rename from chemicals/benzene/leap.log rename to gaff2xml/chemicals/benzene/leap.log diff --git a/chemicals/benzene/sqm.in b/gaff2xml/chemicals/benzene/sqm.in similarity index 100% rename from chemicals/benzene/sqm.in rename to gaff2xml/chemicals/benzene/sqm.in diff --git a/chemicals/benzene/sqm.out b/gaff2xml/chemicals/benzene/sqm.out similarity index 100% rename from chemicals/benzene/sqm.out rename to gaff2xml/chemicals/benzene/sqm.out diff --git a/chemicals/benzene/sqm.pdb b/gaff2xml/chemicals/benzene/sqm.pdb similarity index 100% rename from chemicals/benzene/sqm.pdb rename to gaff2xml/chemicals/benzene/sqm.pdb diff --git a/chemicals/cyclopropane/ANTECHAMBER_AC.AC b/gaff2xml/chemicals/cyclopropane/ANTECHAMBER_AC.AC similarity index 100% rename from chemicals/cyclopropane/ANTECHAMBER_AC.AC rename to gaff2xml/chemicals/cyclopropane/ANTECHAMBER_AC.AC diff --git a/chemicals/cyclopropane/ANTECHAMBER_AC.AC0 b/gaff2xml/chemicals/cyclopropane/ANTECHAMBER_AC.AC0 similarity index 100% rename from chemicals/cyclopropane/ANTECHAMBER_AC.AC0 rename to gaff2xml/chemicals/cyclopropane/ANTECHAMBER_AC.AC0 diff --git a/chemicals/cyclopropane/ANTECHAMBER_AM1BCC.AC b/gaff2xml/chemicals/cyclopropane/ANTECHAMBER_AM1BCC.AC similarity index 100% rename from chemicals/cyclopropane/ANTECHAMBER_AM1BCC.AC rename to gaff2xml/chemicals/cyclopropane/ANTECHAMBER_AM1BCC.AC diff --git a/chemicals/cyclopropane/ANTECHAMBER_AM1BCC_PRE.AC b/gaff2xml/chemicals/cyclopropane/ANTECHAMBER_AM1BCC_PRE.AC similarity index 100% rename from chemicals/cyclopropane/ANTECHAMBER_AM1BCC_PRE.AC rename to gaff2xml/chemicals/cyclopropane/ANTECHAMBER_AM1BCC_PRE.AC diff --git a/chemicals/cyclopropane/ANTECHAMBER_BOND_TYPE.AC b/gaff2xml/chemicals/cyclopropane/ANTECHAMBER_BOND_TYPE.AC similarity index 100% rename from chemicals/cyclopropane/ANTECHAMBER_BOND_TYPE.AC rename to gaff2xml/chemicals/cyclopropane/ANTECHAMBER_BOND_TYPE.AC diff --git a/chemicals/cyclopropane/ANTECHAMBER_BOND_TYPE.AC0 b/gaff2xml/chemicals/cyclopropane/ANTECHAMBER_BOND_TYPE.AC0 similarity index 100% rename from chemicals/cyclopropane/ANTECHAMBER_BOND_TYPE.AC0 rename to gaff2xml/chemicals/cyclopropane/ANTECHAMBER_BOND_TYPE.AC0 diff --git a/chemicals/cyclopropane/ATOMTYPE.INF b/gaff2xml/chemicals/cyclopropane/ATOMTYPE.INF similarity index 100% rename from chemicals/cyclopropane/ATOMTYPE.INF rename to gaff2xml/chemicals/cyclopropane/ATOMTYPE.INF diff --git a/chemicals/cyclopropane/cyclopropane.frcmod b/gaff2xml/chemicals/cyclopropane/cyclopropane.frcmod similarity index 100% rename from chemicals/cyclopropane/cyclopropane.frcmod rename to gaff2xml/chemicals/cyclopropane/cyclopropane.frcmod diff --git a/chemicals/cyclopropane/cyclopropane.inpcrd b/gaff2xml/chemicals/cyclopropane/cyclopropane.inpcrd similarity index 100% rename from chemicals/cyclopropane/cyclopropane.inpcrd rename to gaff2xml/chemicals/cyclopropane/cyclopropane.inpcrd diff --git a/chemicals/cyclopropane/cyclopropane.lib b/gaff2xml/chemicals/cyclopropane/cyclopropane.lib similarity index 100% rename from chemicals/cyclopropane/cyclopropane.lib rename to gaff2xml/chemicals/cyclopropane/cyclopropane.lib diff --git a/chemicals/cyclopropane/cyclopropane.mol2 b/gaff2xml/chemicals/cyclopropane/cyclopropane.mol2 similarity index 100% rename from chemicals/cyclopropane/cyclopropane.mol2 rename to gaff2xml/chemicals/cyclopropane/cyclopropane.mol2 diff --git a/chemicals/cyclopropane/cyclopropane.pdb b/gaff2xml/chemicals/cyclopropane/cyclopropane.pdb similarity index 100% rename from chemicals/cyclopropane/cyclopropane.pdb rename to gaff2xml/chemicals/cyclopropane/cyclopropane.pdb diff --git a/chemicals/cyclopropane/cyclopropane.prmtop b/gaff2xml/chemicals/cyclopropane/cyclopropane.prmtop similarity index 100% rename from chemicals/cyclopropane/cyclopropane.prmtop rename to gaff2xml/chemicals/cyclopropane/cyclopropane.prmtop diff --git a/chemicals/cyclopropane/cyclopropane.sdf b/gaff2xml/chemicals/cyclopropane/cyclopropane.sdf similarity index 100% rename from chemicals/cyclopropane/cyclopropane.sdf rename to gaff2xml/chemicals/cyclopropane/cyclopropane.sdf diff --git a/chemicals/cyclopropane/cyclopropane.xml b/gaff2xml/chemicals/cyclopropane/cyclopropane.xml similarity index 100% rename from chemicals/cyclopropane/cyclopropane.xml rename to gaff2xml/chemicals/cyclopropane/cyclopropane.xml diff --git a/chemicals/cyclopropane/leap.log b/gaff2xml/chemicals/cyclopropane/leap.log similarity index 100% rename from chemicals/cyclopropane/leap.log rename to gaff2xml/chemicals/cyclopropane/leap.log diff --git a/chemicals/cyclopropane/sqm.in b/gaff2xml/chemicals/cyclopropane/sqm.in similarity index 100% rename from chemicals/cyclopropane/sqm.in rename to gaff2xml/chemicals/cyclopropane/sqm.in diff --git a/chemicals/cyclopropane/sqm.out b/gaff2xml/chemicals/cyclopropane/sqm.out similarity index 100% rename from chemicals/cyclopropane/sqm.out rename to gaff2xml/chemicals/cyclopropane/sqm.out diff --git a/chemicals/cyclopropane/sqm.pdb b/gaff2xml/chemicals/cyclopropane/sqm.pdb similarity index 100% rename from chemicals/cyclopropane/sqm.pdb rename to gaff2xml/chemicals/cyclopropane/sqm.pdb diff --git a/chemicals/drugs/README.md b/gaff2xml/chemicals/drugs/README.md similarity index 100% rename from chemicals/drugs/README.md rename to gaff2xml/chemicals/drugs/README.md diff --git a/chemicals/drugs/Zdd.mol2.gz b/gaff2xml/chemicals/drugs/Zdd.mol2.gz similarity index 100% rename from chemicals/drugs/Zdd.mol2.gz rename to gaff2xml/chemicals/drugs/Zdd.mol2.gz diff --git a/chemicals/drugs/usual.mol2.csh b/gaff2xml/chemicals/drugs/usual.mol2.csh similarity index 100% rename from chemicals/drugs/usual.mol2.csh rename to gaff2xml/chemicals/drugs/usual.mol2.csh diff --git a/chemicals/ethene/ANTECHAMBER_AC.AC b/gaff2xml/chemicals/ethene/ANTECHAMBER_AC.AC similarity index 100% rename from chemicals/ethene/ANTECHAMBER_AC.AC rename to gaff2xml/chemicals/ethene/ANTECHAMBER_AC.AC diff --git a/chemicals/ethene/ANTECHAMBER_AC.AC0 b/gaff2xml/chemicals/ethene/ANTECHAMBER_AC.AC0 similarity index 100% rename from chemicals/ethene/ANTECHAMBER_AC.AC0 rename to gaff2xml/chemicals/ethene/ANTECHAMBER_AC.AC0 diff --git a/chemicals/ethene/ANTECHAMBER_AM1BCC.AC b/gaff2xml/chemicals/ethene/ANTECHAMBER_AM1BCC.AC similarity index 100% rename from chemicals/ethene/ANTECHAMBER_AM1BCC.AC rename to gaff2xml/chemicals/ethene/ANTECHAMBER_AM1BCC.AC diff --git a/chemicals/ethene/ANTECHAMBER_AM1BCC_PRE.AC b/gaff2xml/chemicals/ethene/ANTECHAMBER_AM1BCC_PRE.AC similarity index 100% rename from chemicals/ethene/ANTECHAMBER_AM1BCC_PRE.AC rename to gaff2xml/chemicals/ethene/ANTECHAMBER_AM1BCC_PRE.AC diff --git a/chemicals/ethene/ANTECHAMBER_BOND_TYPE.AC b/gaff2xml/chemicals/ethene/ANTECHAMBER_BOND_TYPE.AC similarity index 100% rename from chemicals/ethene/ANTECHAMBER_BOND_TYPE.AC rename to gaff2xml/chemicals/ethene/ANTECHAMBER_BOND_TYPE.AC diff --git a/chemicals/ethene/ANTECHAMBER_BOND_TYPE.AC0 b/gaff2xml/chemicals/ethene/ANTECHAMBER_BOND_TYPE.AC0 similarity index 100% rename from chemicals/ethene/ANTECHAMBER_BOND_TYPE.AC0 rename to gaff2xml/chemicals/ethene/ANTECHAMBER_BOND_TYPE.AC0 diff --git a/chemicals/ethene/ATOMTYPE.INF b/gaff2xml/chemicals/ethene/ATOMTYPE.INF similarity index 100% rename from chemicals/ethene/ATOMTYPE.INF rename to gaff2xml/chemicals/ethene/ATOMTYPE.INF diff --git a/chemicals/ethene/ethene.frcmod b/gaff2xml/chemicals/ethene/ethene.frcmod similarity index 100% rename from chemicals/ethene/ethene.frcmod rename to gaff2xml/chemicals/ethene/ethene.frcmod diff --git a/chemicals/ethene/ethene.inpcrd b/gaff2xml/chemicals/ethene/ethene.inpcrd similarity index 100% rename from chemicals/ethene/ethene.inpcrd rename to gaff2xml/chemicals/ethene/ethene.inpcrd diff --git a/chemicals/ethene/ethene.lib b/gaff2xml/chemicals/ethene/ethene.lib similarity index 100% rename from chemicals/ethene/ethene.lib rename to gaff2xml/chemicals/ethene/ethene.lib diff --git a/chemicals/ethene/ethene.mol2 b/gaff2xml/chemicals/ethene/ethene.mol2 similarity index 100% rename from chemicals/ethene/ethene.mol2 rename to gaff2xml/chemicals/ethene/ethene.mol2 diff --git a/chemicals/ethene/ethene.pdb b/gaff2xml/chemicals/ethene/ethene.pdb similarity index 100% rename from chemicals/ethene/ethene.pdb rename to gaff2xml/chemicals/ethene/ethene.pdb diff --git a/chemicals/ethene/ethene.prmtop b/gaff2xml/chemicals/ethene/ethene.prmtop similarity index 100% rename from chemicals/ethene/ethene.prmtop rename to gaff2xml/chemicals/ethene/ethene.prmtop diff --git a/chemicals/ethene/ethene.sdf b/gaff2xml/chemicals/ethene/ethene.sdf similarity index 100% rename from chemicals/ethene/ethene.sdf rename to gaff2xml/chemicals/ethene/ethene.sdf diff --git a/chemicals/ethene/ethene.xml b/gaff2xml/chemicals/ethene/ethene.xml similarity index 100% rename from chemicals/ethene/ethene.xml rename to gaff2xml/chemicals/ethene/ethene.xml diff --git a/chemicals/ethene/leap.log b/gaff2xml/chemicals/ethene/leap.log similarity index 100% rename from chemicals/ethene/leap.log rename to gaff2xml/chemicals/ethene/leap.log diff --git a/chemicals/ethene/sqm.in b/gaff2xml/chemicals/ethene/sqm.in similarity index 100% rename from chemicals/ethene/sqm.in rename to gaff2xml/chemicals/ethene/sqm.in diff --git a/chemicals/ethene/sqm.out b/gaff2xml/chemicals/ethene/sqm.out similarity index 100% rename from chemicals/ethene/sqm.out rename to gaff2xml/chemicals/ethene/sqm.out diff --git a/chemicals/ethene/sqm.pdb b/gaff2xml/chemicals/ethene/sqm.pdb similarity index 100% rename from chemicals/ethene/sqm.pdb rename to gaff2xml/chemicals/ethene/sqm.pdb diff --git a/chemicals/etoh/ANTECHAMBER_AC.AC b/gaff2xml/chemicals/etoh/ANTECHAMBER_AC.AC similarity index 100% rename from chemicals/etoh/ANTECHAMBER_AC.AC rename to gaff2xml/chemicals/etoh/ANTECHAMBER_AC.AC diff --git a/chemicals/etoh/ANTECHAMBER_AC.AC0 b/gaff2xml/chemicals/etoh/ANTECHAMBER_AC.AC0 similarity index 100% rename from chemicals/etoh/ANTECHAMBER_AC.AC0 rename to gaff2xml/chemicals/etoh/ANTECHAMBER_AC.AC0 diff --git a/chemicals/etoh/ANTECHAMBER_AM1BCC.AC b/gaff2xml/chemicals/etoh/ANTECHAMBER_AM1BCC.AC similarity index 100% rename from chemicals/etoh/ANTECHAMBER_AM1BCC.AC rename to gaff2xml/chemicals/etoh/ANTECHAMBER_AM1BCC.AC diff --git a/chemicals/etoh/ANTECHAMBER_AM1BCC_PRE.AC b/gaff2xml/chemicals/etoh/ANTECHAMBER_AM1BCC_PRE.AC similarity index 100% rename from chemicals/etoh/ANTECHAMBER_AM1BCC_PRE.AC rename to gaff2xml/chemicals/etoh/ANTECHAMBER_AM1BCC_PRE.AC diff --git a/chemicals/etoh/ANTECHAMBER_BOND_TYPE.AC b/gaff2xml/chemicals/etoh/ANTECHAMBER_BOND_TYPE.AC similarity index 100% rename from chemicals/etoh/ANTECHAMBER_BOND_TYPE.AC rename to gaff2xml/chemicals/etoh/ANTECHAMBER_BOND_TYPE.AC diff --git a/chemicals/etoh/ANTECHAMBER_BOND_TYPE.AC0 b/gaff2xml/chemicals/etoh/ANTECHAMBER_BOND_TYPE.AC0 similarity index 100% rename from chemicals/etoh/ANTECHAMBER_BOND_TYPE.AC0 rename to gaff2xml/chemicals/etoh/ANTECHAMBER_BOND_TYPE.AC0 diff --git a/chemicals/etoh/ATOMTYPE.INF b/gaff2xml/chemicals/etoh/ATOMTYPE.INF similarity index 100% rename from chemicals/etoh/ATOMTYPE.INF rename to gaff2xml/chemicals/etoh/ATOMTYPE.INF diff --git a/chemicals/etoh/etoh.frcmod b/gaff2xml/chemicals/etoh/etoh.frcmod similarity index 100% rename from chemicals/etoh/etoh.frcmod rename to gaff2xml/chemicals/etoh/etoh.frcmod diff --git a/chemicals/etoh/etoh.inpcrd b/gaff2xml/chemicals/etoh/etoh.inpcrd similarity index 100% rename from chemicals/etoh/etoh.inpcrd rename to gaff2xml/chemicals/etoh/etoh.inpcrd diff --git a/chemicals/etoh/etoh.lib b/gaff2xml/chemicals/etoh/etoh.lib similarity index 100% rename from chemicals/etoh/etoh.lib rename to gaff2xml/chemicals/etoh/etoh.lib diff --git a/chemicals/etoh/etoh.mol2 b/gaff2xml/chemicals/etoh/etoh.mol2 similarity index 100% rename from chemicals/etoh/etoh.mol2 rename to gaff2xml/chemicals/etoh/etoh.mol2 diff --git a/chemicals/etoh/etoh.pdb b/gaff2xml/chemicals/etoh/etoh.pdb similarity index 100% rename from chemicals/etoh/etoh.pdb rename to gaff2xml/chemicals/etoh/etoh.pdb diff --git a/chemicals/etoh/etoh.prmtop b/gaff2xml/chemicals/etoh/etoh.prmtop similarity index 100% rename from chemicals/etoh/etoh.prmtop rename to gaff2xml/chemicals/etoh/etoh.prmtop diff --git a/chemicals/etoh/etoh.sdf b/gaff2xml/chemicals/etoh/etoh.sdf similarity index 100% rename from chemicals/etoh/etoh.sdf rename to gaff2xml/chemicals/etoh/etoh.sdf diff --git a/chemicals/etoh/etoh.xml b/gaff2xml/chemicals/etoh/etoh.xml similarity index 100% rename from chemicals/etoh/etoh.xml rename to gaff2xml/chemicals/etoh/etoh.xml diff --git a/chemicals/etoh/leap.log b/gaff2xml/chemicals/etoh/leap.log similarity index 100% rename from chemicals/etoh/leap.log rename to gaff2xml/chemicals/etoh/leap.log diff --git a/chemicals/etoh/sqm.in b/gaff2xml/chemicals/etoh/sqm.in similarity index 100% rename from chemicals/etoh/sqm.in rename to gaff2xml/chemicals/etoh/sqm.in diff --git a/chemicals/etoh/sqm.out b/gaff2xml/chemicals/etoh/sqm.out similarity index 100% rename from chemicals/etoh/sqm.out rename to gaff2xml/chemicals/etoh/sqm.out diff --git a/chemicals/etoh/sqm.pdb b/gaff2xml/chemicals/etoh/sqm.pdb similarity index 100% rename from chemicals/etoh/sqm.pdb rename to gaff2xml/chemicals/etoh/sqm.pdb diff --git a/chemicals/imatinib/ANTECHAMBER_AC.AC b/gaff2xml/chemicals/imatinib/ANTECHAMBER_AC.AC similarity index 100% rename from chemicals/imatinib/ANTECHAMBER_AC.AC rename to gaff2xml/chemicals/imatinib/ANTECHAMBER_AC.AC diff --git a/chemicals/imatinib/ANTECHAMBER_AC.AC0 b/gaff2xml/chemicals/imatinib/ANTECHAMBER_AC.AC0 similarity index 100% rename from chemicals/imatinib/ANTECHAMBER_AC.AC0 rename to gaff2xml/chemicals/imatinib/ANTECHAMBER_AC.AC0 diff --git a/chemicals/imatinib/ANTECHAMBER_AM1BCC.AC b/gaff2xml/chemicals/imatinib/ANTECHAMBER_AM1BCC.AC similarity index 100% rename from chemicals/imatinib/ANTECHAMBER_AM1BCC.AC rename to gaff2xml/chemicals/imatinib/ANTECHAMBER_AM1BCC.AC diff --git a/chemicals/imatinib/ANTECHAMBER_AM1BCC_PRE.AC b/gaff2xml/chemicals/imatinib/ANTECHAMBER_AM1BCC_PRE.AC similarity index 100% rename from chemicals/imatinib/ANTECHAMBER_AM1BCC_PRE.AC rename to gaff2xml/chemicals/imatinib/ANTECHAMBER_AM1BCC_PRE.AC diff --git a/chemicals/imatinib/ANTECHAMBER_BOND_TYPE.AC b/gaff2xml/chemicals/imatinib/ANTECHAMBER_BOND_TYPE.AC similarity index 100% rename from chemicals/imatinib/ANTECHAMBER_BOND_TYPE.AC rename to gaff2xml/chemicals/imatinib/ANTECHAMBER_BOND_TYPE.AC diff --git a/chemicals/imatinib/ANTECHAMBER_BOND_TYPE.AC0 b/gaff2xml/chemicals/imatinib/ANTECHAMBER_BOND_TYPE.AC0 similarity index 100% rename from chemicals/imatinib/ANTECHAMBER_BOND_TYPE.AC0 rename to gaff2xml/chemicals/imatinib/ANTECHAMBER_BOND_TYPE.AC0 diff --git a/chemicals/imatinib/ATOMTYPE.INF b/gaff2xml/chemicals/imatinib/ATOMTYPE.INF similarity index 100% rename from chemicals/imatinib/ATOMTYPE.INF rename to gaff2xml/chemicals/imatinib/ATOMTYPE.INF diff --git a/chemicals/imatinib/imatinib.frcmod b/gaff2xml/chemicals/imatinib/imatinib.frcmod similarity index 100% rename from chemicals/imatinib/imatinib.frcmod rename to gaff2xml/chemicals/imatinib/imatinib.frcmod diff --git a/chemicals/imatinib/imatinib.inpcrd b/gaff2xml/chemicals/imatinib/imatinib.inpcrd similarity index 100% rename from chemicals/imatinib/imatinib.inpcrd rename to gaff2xml/chemicals/imatinib/imatinib.inpcrd diff --git a/chemicals/imatinib/imatinib.lib b/gaff2xml/chemicals/imatinib/imatinib.lib similarity index 100% rename from chemicals/imatinib/imatinib.lib rename to gaff2xml/chemicals/imatinib/imatinib.lib diff --git a/chemicals/imatinib/imatinib.mol2 b/gaff2xml/chemicals/imatinib/imatinib.mol2 similarity index 100% rename from chemicals/imatinib/imatinib.mol2 rename to gaff2xml/chemicals/imatinib/imatinib.mol2 diff --git a/chemicals/imatinib/imatinib.pdb b/gaff2xml/chemicals/imatinib/imatinib.pdb similarity index 100% rename from chemicals/imatinib/imatinib.pdb rename to gaff2xml/chemicals/imatinib/imatinib.pdb diff --git a/chemicals/imatinib/imatinib.prmtop b/gaff2xml/chemicals/imatinib/imatinib.prmtop similarity index 100% rename from chemicals/imatinib/imatinib.prmtop rename to gaff2xml/chemicals/imatinib/imatinib.prmtop diff --git a/chemicals/imatinib/imatinib.sdf b/gaff2xml/chemicals/imatinib/imatinib.sdf similarity index 100% rename from chemicals/imatinib/imatinib.sdf rename to gaff2xml/chemicals/imatinib/imatinib.sdf diff --git a/chemicals/imatinib/imatinib.xml b/gaff2xml/chemicals/imatinib/imatinib.xml similarity index 100% rename from chemicals/imatinib/imatinib.xml rename to gaff2xml/chemicals/imatinib/imatinib.xml diff --git a/chemicals/imatinib/leap.log b/gaff2xml/chemicals/imatinib/leap.log similarity index 100% rename from chemicals/imatinib/leap.log rename to gaff2xml/chemicals/imatinib/leap.log diff --git a/chemicals/imatinib/sqm.in b/gaff2xml/chemicals/imatinib/sqm.in similarity index 100% rename from chemicals/imatinib/sqm.in rename to gaff2xml/chemicals/imatinib/sqm.in diff --git a/chemicals/imatinib/sqm.out b/gaff2xml/chemicals/imatinib/sqm.out similarity index 100% rename from chemicals/imatinib/sqm.out rename to gaff2xml/chemicals/imatinib/sqm.out diff --git a/chemicals/imatinib/sqm.pdb b/gaff2xml/chemicals/imatinib/sqm.pdb similarity index 100% rename from chemicals/imatinib/sqm.pdb rename to gaff2xml/chemicals/imatinib/sqm.pdb diff --git a/chemicals/propene/ANTECHAMBER_AC.AC b/gaff2xml/chemicals/propene/ANTECHAMBER_AC.AC similarity index 100% rename from chemicals/propene/ANTECHAMBER_AC.AC rename to gaff2xml/chemicals/propene/ANTECHAMBER_AC.AC diff --git a/chemicals/propene/ANTECHAMBER_AC.AC0 b/gaff2xml/chemicals/propene/ANTECHAMBER_AC.AC0 similarity index 100% rename from chemicals/propene/ANTECHAMBER_AC.AC0 rename to gaff2xml/chemicals/propene/ANTECHAMBER_AC.AC0 diff --git a/chemicals/propene/ANTECHAMBER_AM1BCC.AC b/gaff2xml/chemicals/propene/ANTECHAMBER_AM1BCC.AC similarity index 100% rename from chemicals/propene/ANTECHAMBER_AM1BCC.AC rename to gaff2xml/chemicals/propene/ANTECHAMBER_AM1BCC.AC diff --git a/chemicals/propene/ANTECHAMBER_AM1BCC_PRE.AC b/gaff2xml/chemicals/propene/ANTECHAMBER_AM1BCC_PRE.AC similarity index 100% rename from chemicals/propene/ANTECHAMBER_AM1BCC_PRE.AC rename to gaff2xml/chemicals/propene/ANTECHAMBER_AM1BCC_PRE.AC diff --git a/chemicals/propene/ANTECHAMBER_BOND_TYPE.AC b/gaff2xml/chemicals/propene/ANTECHAMBER_BOND_TYPE.AC similarity index 100% rename from chemicals/propene/ANTECHAMBER_BOND_TYPE.AC rename to gaff2xml/chemicals/propene/ANTECHAMBER_BOND_TYPE.AC diff --git a/chemicals/propene/ANTECHAMBER_BOND_TYPE.AC0 b/gaff2xml/chemicals/propene/ANTECHAMBER_BOND_TYPE.AC0 similarity index 100% rename from chemicals/propene/ANTECHAMBER_BOND_TYPE.AC0 rename to gaff2xml/chemicals/propene/ANTECHAMBER_BOND_TYPE.AC0 diff --git a/chemicals/propene/ATOMTYPE.INF b/gaff2xml/chemicals/propene/ATOMTYPE.INF similarity index 100% rename from chemicals/propene/ATOMTYPE.INF rename to gaff2xml/chemicals/propene/ATOMTYPE.INF diff --git a/chemicals/propene/leap.log b/gaff2xml/chemicals/propene/leap.log similarity index 100% rename from chemicals/propene/leap.log rename to gaff2xml/chemicals/propene/leap.log diff --git a/chemicals/propene/propene.frcmod b/gaff2xml/chemicals/propene/propene.frcmod similarity index 100% rename from chemicals/propene/propene.frcmod rename to gaff2xml/chemicals/propene/propene.frcmod diff --git a/chemicals/propene/propene.inpcrd b/gaff2xml/chemicals/propene/propene.inpcrd similarity index 100% rename from chemicals/propene/propene.inpcrd rename to gaff2xml/chemicals/propene/propene.inpcrd diff --git a/chemicals/propene/propene.lib b/gaff2xml/chemicals/propene/propene.lib similarity index 100% rename from chemicals/propene/propene.lib rename to gaff2xml/chemicals/propene/propene.lib diff --git a/chemicals/propene/propene.mol2 b/gaff2xml/chemicals/propene/propene.mol2 similarity index 100% rename from chemicals/propene/propene.mol2 rename to gaff2xml/chemicals/propene/propene.mol2 diff --git a/chemicals/propene/propene.pdb b/gaff2xml/chemicals/propene/propene.pdb similarity index 100% rename from chemicals/propene/propene.pdb rename to gaff2xml/chemicals/propene/propene.pdb diff --git a/chemicals/propene/propene.prmtop b/gaff2xml/chemicals/propene/propene.prmtop similarity index 100% rename from chemicals/propene/propene.prmtop rename to gaff2xml/chemicals/propene/propene.prmtop diff --git a/chemicals/propene/propene.sdf b/gaff2xml/chemicals/propene/propene.sdf similarity index 100% rename from chemicals/propene/propene.sdf rename to gaff2xml/chemicals/propene/propene.sdf diff --git a/chemicals/propene/propene.xml b/gaff2xml/chemicals/propene/propene.xml similarity index 100% rename from chemicals/propene/propene.xml rename to gaff2xml/chemicals/propene/propene.xml diff --git a/chemicals/propene/sqm.in b/gaff2xml/chemicals/propene/sqm.in similarity index 100% rename from chemicals/propene/sqm.in rename to gaff2xml/chemicals/propene/sqm.in diff --git a/chemicals/propene/sqm.out b/gaff2xml/chemicals/propene/sqm.out similarity index 100% rename from chemicals/propene/sqm.out rename to gaff2xml/chemicals/propene/sqm.out diff --git a/chemicals/propene/sqm.pdb b/gaff2xml/chemicals/propene/sqm.pdb similarity index 100% rename from chemicals/propene/sqm.pdb rename to gaff2xml/chemicals/propene/sqm.pdb diff --git a/chemicals/sustiva/ANTECHAMBER_AC.AC b/gaff2xml/chemicals/sustiva/ANTECHAMBER_AC.AC similarity index 100% rename from chemicals/sustiva/ANTECHAMBER_AC.AC rename to gaff2xml/chemicals/sustiva/ANTECHAMBER_AC.AC diff --git a/chemicals/sustiva/ANTECHAMBER_AC.AC0 b/gaff2xml/chemicals/sustiva/ANTECHAMBER_AC.AC0 similarity index 100% rename from chemicals/sustiva/ANTECHAMBER_AC.AC0 rename to gaff2xml/chemicals/sustiva/ANTECHAMBER_AC.AC0 diff --git a/chemicals/sustiva/ANTECHAMBER_AM1BCC.AC b/gaff2xml/chemicals/sustiva/ANTECHAMBER_AM1BCC.AC similarity index 100% rename from chemicals/sustiva/ANTECHAMBER_AM1BCC.AC rename to gaff2xml/chemicals/sustiva/ANTECHAMBER_AM1BCC.AC diff --git a/chemicals/sustiva/ANTECHAMBER_AM1BCC_PRE.AC b/gaff2xml/chemicals/sustiva/ANTECHAMBER_AM1BCC_PRE.AC similarity index 100% rename from chemicals/sustiva/ANTECHAMBER_AM1BCC_PRE.AC rename to gaff2xml/chemicals/sustiva/ANTECHAMBER_AM1BCC_PRE.AC diff --git a/chemicals/sustiva/ANTECHAMBER_BOND_TYPE.AC b/gaff2xml/chemicals/sustiva/ANTECHAMBER_BOND_TYPE.AC similarity index 100% rename from chemicals/sustiva/ANTECHAMBER_BOND_TYPE.AC rename to gaff2xml/chemicals/sustiva/ANTECHAMBER_BOND_TYPE.AC diff --git a/chemicals/sustiva/ANTECHAMBER_BOND_TYPE.AC0 b/gaff2xml/chemicals/sustiva/ANTECHAMBER_BOND_TYPE.AC0 similarity index 100% rename from chemicals/sustiva/ANTECHAMBER_BOND_TYPE.AC0 rename to gaff2xml/chemicals/sustiva/ANTECHAMBER_BOND_TYPE.AC0 diff --git a/chemicals/sustiva/ATOMTYPE.INF b/gaff2xml/chemicals/sustiva/ATOMTYPE.INF similarity index 100% rename from chemicals/sustiva/ATOMTYPE.INF rename to gaff2xml/chemicals/sustiva/ATOMTYPE.INF diff --git a/chemicals/sustiva/leap.log b/gaff2xml/chemicals/sustiva/leap.log similarity index 100% rename from chemicals/sustiva/leap.log rename to gaff2xml/chemicals/sustiva/leap.log diff --git a/chemicals/sustiva/sqm.in b/gaff2xml/chemicals/sustiva/sqm.in similarity index 100% rename from chemicals/sustiva/sqm.in rename to gaff2xml/chemicals/sustiva/sqm.in diff --git a/chemicals/sustiva/sqm.out b/gaff2xml/chemicals/sustiva/sqm.out similarity index 100% rename from chemicals/sustiva/sqm.out rename to gaff2xml/chemicals/sustiva/sqm.out diff --git a/chemicals/sustiva/sqm.pdb b/gaff2xml/chemicals/sustiva/sqm.pdb similarity index 100% rename from chemicals/sustiva/sqm.pdb rename to gaff2xml/chemicals/sustiva/sqm.pdb diff --git a/chemicals/sustiva/sus.lib b/gaff2xml/chemicals/sustiva/sus.lib similarity index 100% rename from chemicals/sustiva/sus.lib rename to gaff2xml/chemicals/sustiva/sus.lib diff --git a/chemicals/sustiva/sustiva.frcmod b/gaff2xml/chemicals/sustiva/sustiva.frcmod similarity index 100% rename from chemicals/sustiva/sustiva.frcmod rename to gaff2xml/chemicals/sustiva/sustiva.frcmod diff --git a/chemicals/sustiva/sustiva.frcmod.xml b/gaff2xml/chemicals/sustiva/sustiva.frcmod.xml similarity index 100% rename from chemicals/sustiva/sustiva.frcmod.xml rename to gaff2xml/chemicals/sustiva/sustiva.frcmod.xml diff --git a/chemicals/sustiva/sustiva.inpcrd b/gaff2xml/chemicals/sustiva/sustiva.inpcrd similarity index 100% rename from chemicals/sustiva/sustiva.inpcrd rename to gaff2xml/chemicals/sustiva/sustiva.inpcrd diff --git a/chemicals/sustiva/sustiva.lib b/gaff2xml/chemicals/sustiva/sustiva.lib similarity index 100% rename from chemicals/sustiva/sustiva.lib rename to gaff2xml/chemicals/sustiva/sustiva.lib diff --git a/chemicals/sustiva/sustiva.mol2 b/gaff2xml/chemicals/sustiva/sustiva.mol2 similarity index 100% rename from chemicals/sustiva/sustiva.mol2 rename to gaff2xml/chemicals/sustiva/sustiva.mol2 diff --git a/chemicals/sustiva/sustiva.pdb b/gaff2xml/chemicals/sustiva/sustiva.pdb similarity index 100% rename from chemicals/sustiva/sustiva.pdb rename to gaff2xml/chemicals/sustiva/sustiva.pdb diff --git a/chemicals/sustiva/sustiva.prmtop b/gaff2xml/chemicals/sustiva/sustiva.prmtop similarity index 100% rename from chemicals/sustiva/sustiva.prmtop rename to gaff2xml/chemicals/sustiva/sustiva.prmtop diff --git a/chemicals/sustiva/sustiva.sdf b/gaff2xml/chemicals/sustiva/sustiva.sdf similarity index 100% rename from chemicals/sustiva/sustiva.sdf rename to gaff2xml/chemicals/sustiva/sustiva.sdf diff --git a/chemicals/sustiva/sustiva.xml b/gaff2xml/chemicals/sustiva/sustiva.xml similarity index 100% rename from chemicals/sustiva/sustiva.xml rename to gaff2xml/chemicals/sustiva/sustiva.xml diff --git a/chemicals/sustiva/test.pdb b/gaff2xml/chemicals/sustiva/test.pdb similarity index 100% rename from chemicals/sustiva/test.pdb rename to gaff2xml/chemicals/sustiva/test.pdb diff --git a/gaff2xml/gafftools.py b/gaff2xml/gafftools.py index 6d61ba9..9bc30bb 100644 --- a/gaff2xml/gafftools.py +++ b/gaff2xml/gafftools.py @@ -2,108 +2,140 @@ import numpy as np import pandas as pd import cStringIO -import simtk.openmm.app.element -import simtk.unit as unit import itertools import logging logger = logging.getLogger(__name__) -gaff_elements = {'br': 'Br', - 'c': 'C', - 'c1': 'C', - 'c2': 'C', - 'c3': 'C', - 'ca': 'C', - 'cc': 'C', - 'cd': 'C', - 'ce': 'C', - 'cf': 'C', - 'cg': 'C', - 'ch': 'C', - 'cl': 'Cl', - 'cp': 'C', - 'cq': 'C', - 'cu': 'C', - 'cv': 'C', - 'cx': 'C', - 'cy': 'C', - 'cz': 'C', - 'f': 'F', - 'h1': 'H', - 'h2': 'H', - 'h3': 'H', - 'h4': 'H', - 'h5': 'H', - 'ha': 'H', - 'hc': 'H', - 'hn': 'H', - 'ho': 'H', - 'hp': 'H', - 'hs': 'H', - 'hw': 'H', - 'hx': 'H', - 'i': 'I', - 'n': 'N', - 'n1': 'N', - 'n2': 'N', - 'n3': 'N', - 'n4': 'N', - 'na': 'N', - 'nb': 'N', - 'nc': 'N', - 'nd': 'N', - 'ne': 'N', - 'nf': 'N', - 'nh': 'N', - 'no': 'N', - 'o': 'O', - 'oh': 'O', - 'os': 'O', - 'ow': 'O', - 'p2': 'P', - 'p3': 'P', - 'p4': 'P', - 'p5': 'P', - 'pb': 'P', - 'px': 'P', - 'py': 'P', - 's': 'S', - 's2': 'S', - 's4': 'S', - 's6': 'S', - 'sh': 'S', - 'ss': 'S', - 'sx': 'S', - 'sy': 'S'} - -def parse_mol2_sections(x): +gaff_elements = { + 'br': 'Br', + 'c': 'C', + 'c1': 'C', + 'c2': 'C', + 'c3': 'C', + 'ca': 'C', + 'cc': 'C', + 'cd': 'C', + 'ce': 'C', + 'cf': 'C', + 'cg': 'C', + 'ch': 'C', + 'cl': 'Cl', + 'cp': 'C', + 'cq': 'C', + 'cu': 'C', + 'cv': 'C', + 'cx': 'C', + 'cy': 'C', + 'cz': 'C', + 'f': 'F', + 'h1': 'H', + 'h2': 'H', + 'h3': 'H', + 'h4': 'H', + 'h5': 'H', + 'ha': 'H', + 'hc': 'H', + 'hn': 'H', + 'ho': 'H', + 'hp': 'H', + 'hs': 'H', + 'hw': 'H', + 'hx': 'H', + 'i': 'I', + 'n': 'N', + 'n1': 'N', + 'n2': 'N', + 'n3': 'N', + 'n4': 'N', + 'na': 'N', + 'nb': 'N', + 'nc': 'N', + 'nd': 'N', + 'ne': 'N', + 'nf': 'N', + 'nh': 'N', + 'no': 'N', + 'o': 'O', + 'oh': 'O', + 'os': 'O', + 'ow': 'O', + 'p2': 'P', + 'p3': 'P', + 'p4': 'P', + 'p5': 'P', + 'pb': 'P', + 'px': 'P', + 'py': 'P', + 's': 'S', + 's2': 'S', + 's4': 'S', + 's6': 'S', + 'sh': 'S', + 'ss': 'S', + 'sx': 'S', + 'sy': 'S'} + + +def parse_mol2_sections(x): + """Helper function for parsing a section in a MOL2 file.""" if x.startswith('@'): parse_mol2_sections.key = x return parse_mol2_sections.key + def mol2_to_dataframes(filename): - """Convert a GAFF mol2 file to a pair of pandas dataframes.""" + """Convert a GAFF mol2 file to a pair of pandas dataframes. + + + Parameters + ---------- + filename : str + Name of mol2 filename + + Returns + ------- + atoms_frame : pd.DataFrame + DataFrame containing atom information + bonds_frame : pd.DataFrame + DataFrame containing bond information + """ with open(filename) as f: data = dict((key, list(grp)) for key, grp in itertools.groupby(f, parse_mol2_sections)) csv = cStringIO.StringIO() csv.writelines(data["@BOND\n"][1:]) csv.reset() - bonds_frame = pd.read_table(csv, delim_whitespace=True, names=["bond_id", "id0","id1", "bond_type"], index_col=0, header=None, sep="\s*",) + bonds_frame = pd.read_table(csv, delim_whitespace=True, names=["bond_id", "id0", "id1", "bond_type"], index_col=0, header=None, sep="\s*") csv = cStringIO.StringIO() csv.writelines(data["@ATOM\n"][1:]) csv.reset() - atoms_frame = pd.read_csv(csv, delim_whitespace=True, names=["serial", "name", "x", "y" ,"z", "atype", "code", "resName", "charge"], header=None, usecols=range(1, 10)) + atoms_frame = pd.read_csv(csv, delim_whitespace=True, names=["serial", "name", "x", "y", "z", "atype", "code", "resName", "charge"], header=None, usecols=range(1, 10)) return atoms_frame, bonds_frame + class Mol2Parser(object): def __init__(self, filename): + """Create Mol2Parser object containing parsed Mol2 data. + + Parameters + ---------- + filename : str + Name of mol2 filename + + """ self.atoms, self.bonds = mol2_to_dataframes(filename) def to_mdtraj(self): + """Return parsed Mol2 as a MDTraj trajectory + + Returns + ------- + traj : MDTraj.Trajectory + Trajectory containing Mol2 data in first frame + """ atoms, bonds = self.atoms, self.bonds atoms_mdtraj = atoms[["name", "resName"]] atoms_mdtraj["serial"] = atoms.index @@ -116,16 +148,23 @@ def to_mdtraj(self): bonds_mdtraj -= offset top = mdtraj.Topology.from_dataframe(atoms_mdtraj, bonds_mdtraj) - xyzlist = np.array([atoms[["x","y","z"]].values]) + xyzlist = np.array([atoms[["x", "y", "z"]].values]) xyzlist /= 10.0 # Convert from angstrom to nanometer traj = mdtraj.Trajectory(xyzlist, top) return traj def to_openmm(self): + """Return parsed Mol2 in OpenMM format + + Returns + ------- + top_mm : simtk.openmm.app.Topology + Topology representing Mol2 object + xyz_mm : list + List of xyz coordinates of atoms [nanometers] + """ traj = self.to_mdtraj() top_mm = traj.top.to_openmm() xyz_mm = traj.xyz[0].tolist() return top_mm, xyz_mm - - diff --git a/gaff2xml/system_checker.py b/gaff2xml/system_checker.py index 59cae1f..9da1bbe 100644 --- a/gaff2xml/system_checker.py +++ b/gaff2xml/system_checker.py @@ -1,4 +1,3 @@ -import simtk.unit as u import numpy as np import itertools import simtk.openmm as mm @@ -8,44 +7,104 @@ EPSILON = 1E-4 # Error tolerance for differences in parameters. Typically for relative differences, but sometimes for absolute. + +def compare(x0, x1, relative=False): + """Compare two quantities relative to EPSILON.""" + + if relative is True: + denominator = abs(x1) + else: + denominator = 1.0 + + return (abs(x0 - x1) / denominator) < EPSILON + reduce_precision = lambda x: float(np.float16(x)) # Useful for creating dictionary keys with floating point numbers that may differ at insignificant decimal places reorder_bonds = lambda i0, i1: (min(i0, i1), max(i0, i1)) reorder_angles = lambda i0, i1, i2: (min(i0, i2), i1, max(i0, i2)) + def reorder_proper_torsions(i0, i1, i2, i3): + """Return the atom indices of a proper torsion after "flipping" the + order so that the first atom is the smallest index. + + Parameters + ---------- + i0, i1, i2, i3 : int, + Atom indices of torsion + + Returns + ------- + j0, j1, j2, j3 : int, + Atom indices of torsion + + """ if i0 < i3: j0, j1, j2, j3 = i0, i1, i2, i3 else: - j0, j1, j2, j3 = i3, i2, i1, i0 + j0, j1, j2, j3 = i3, i2, i1, i0 return j0, j1, j2, j3 + def reorder_improper_torsions(i0, i1, i2, i3, bond_set): """Return j0, j1, j2, j3, where j0 is the central index and j1, j2, j3 - are in sorted() order. Centrality is determined by the maximum counts - in the adjacency matrix. + are in sorted() order. + + Parameters + ---------- + i0, i1, i2, i3 : int, + Atom indices of torsion + bond_set : set containing the index pairs between which a bond is defined + + Returns + ------- + j0, j1, j2, j3 : int, + Atom indices of torsion, with j0 being the central index + + Notes + ----- + + Centrality is determined by the maximum counts in the adjacency matrix. + """ connections = np.zeros((4, 4)) - mapping = {i0:0, i1:1, i2:2, i3:3} + mapping = {i0: 0, i1: 1, i2: 2, i3: 3} inv_mapping = dict([(val, key) for key, val in mapping.iteritems()]) - for (a,b) in itertools.combinations([i0, i1, i2, i3], 2): - if (a,b) in bond_set: + for (a, b) in itertools.combinations([i0, i1, i2, i3], 2): + if (a, b) in bond_set: i, j = mapping[a], mapping[b] connections[i, j] += 1. connections[j, i] += 1. - + central_ind = connections.sum(0).argmax() central_ind = inv_mapping[central_ind] other_ind = sorted([i0, i1, i2, i3]) other_ind.remove(central_ind) - + return central_ind, other_ind[0], other_ind[1], other_ind[2] + def get_symmetrized_bond_set(bond_force): + """Return a set containing atom pairs connected by bonds. + + Parameters + ---------- + bond_force : mm.HarmonicBondForce + The bond force of an OpenMM system + + Returns + ------- + bond_set : a set containing pairs of atoms connected by bonds: + + Notes + ----- + The resulting set is symmetric: if (i,j) in S, then (j,i) in S. + """ + bond_set = set() n_bonds = bond_force.getNumBonds() @@ -56,12 +115,14 @@ def get_symmetrized_bond_set(bond_force): return bond_set + def is_proper(i0, i1, i2, i3, bond_set): """Check for three sequential bonds and atom uniqueness.""" - if (i0, i1) in bond_set and (i1, i2) in bond_set and (i2, i3) in bond_set and len(set([i0, i1, i2, i3])) == 4: + if (i0, i1) in bond_set and (i1, i2) in bond_set and (i2, i3) in bond_set and len(set([i0, i1, i2, i3])) == 4: return True return False + def is_improper(i0, i1, i2, i3, bond_set): """Check for three non-sequential bonds and atom uniqueness.""" if len(set([i0, i1, i2, i3])) == 4: @@ -69,22 +130,24 @@ def is_improper(i0, i1, i2, i3, bond_set): return True return False + class SystemChecker(object): + def __init__(self, simulation0, simulation1): """Create a SystemChecker object that compares forces in simulation0 and simulation1. - + Parameters ---------- simulation0 : OpenMM Simulation simulation1 : OpenMM Simulation - + Notes ----- - + You MUST have constraints=None when creating your simulation objects. - + """ - + self.simulation0 = simulation0 self.simulation1 = simulation1 @@ -107,8 +170,10 @@ def __init__(self, simulation0, simulation1): self.torsion_force1 = force elif type(force) == mm.NonbondedForce: self.nonbonded_force1 = force - + def check_force_parameters(self): + """Check that force parameters are the same, up to some equivalence. + """ self.check_bonds(self.bond_force0, self.bond_force1) self.check_angles(self.angle_force0, self.angle_force1) self.check_nonbonded(self.nonbonded_force0, self.nonbonded_force1) @@ -117,77 +182,119 @@ def check_force_parameters(self): logger.info("Note: skipping degenerate impropers with < 4 atoms.") def check_bonds(self, force0, force1): - - assert force0.getNumBonds() == force1.getNumBonds(), "Error: Systems have %d and %d entries in HarmonicBondForce, respectively." % (force0.getNumBonds(), force1.getNumBonds()) - n_bonds = force0.getNumBonds() + """Check that force0 and force1 are equivalent Bond forces. + + + Parameters + ---------- + force0 : mm.HarmonicBondForce + force1 : mm.HarmonicBondForce + + """ + + assert type(force0) == type(force1), "Error: force0 and force1 must be the same type." + assert type(force0) == mm.HarmonicBondForce, "Error: forces must be HarmonicBondForces" + + n_bonds0 = force0.getNumBonds() + n_bonds1 = force1.getNumBonds() dict0, dict1 = {}, {} - + i0, i1, r0, k0 = force0.getBondParameters(0) unit_r = r0.unit unit_k = k0.unit - for k in range(n_bonds): + for k in range(n_bonds0): i0, i1, r0, k0 = force0.getBondParameters(k) i0, i1 = reorder_bonds(i0, i1) - dict0[i0, i1] = ((r0 / unit_r, k0 / unit_k)) + if k0 / k0.unit != 0.0: # Skip forces with strength 0.0 + dict0[i0, i1] = ((r0 / unit_r, k0 / unit_k)) + for k in range(n_bonds1): i0, i1, r0, k0 = force1.getBondParameters(k) i0, i1 = reorder_bonds(i0, i1) - dict1[i0, i1] = ((r0 / unit_r, k0 / unit_k)) + if k0 / k0.unit != 0.0: # Skip forces with strength 0.0 + dict1[i0, i1] = ((r0 / unit_r, k0 / unit_k)) + keys0 = set(dict0.keys()) + keys1 = set(dict1.keys()) + logger.info("Bonds0 - Bonds1 = %s" % (keys0.difference(keys1))) + logger.info("Bonds1 - Bonds0 = %s" % (keys1.difference(keys0))) assert set(dict0.keys()) == set(dict1.keys()), "Systems have different HarmonicBond Forces" for k, parameter_name in enumerate(["r0", "k0"]): for (i0, i1) in dict0.keys(): val0 = dict0[i0, i1][k] val1 = dict1[i0, i1][k] - assert (abs(val0 - val1) / val0) < EPSILON, "Error: Harmonic Bond (%d, %d) has %s values of %f and %f, respectively." % (i0, i1, parameter_name, val0, val1) - + assert compare(val0, val1), "Error: Harmonic Bond (%d, %d) has %s values of %f and %f, respectively." % (i0, i1, parameter_name, val0, val1) def check_angles(self, force0, force1): - - #We can't assert numAngles are equal because one might have "blank" forces with constant 0.0 - #assert force0.getNumAngles() == force1.getNumAngles(), "Error: Systems have %d and %d entries in HarmonicAngleForce, respectively." % (force0.getNumAngles(), force1.getNumAngles()) - + """Check that force0 and force1 are equivalent Angle forces. + + + Parameters + ---------- + force0 : mm.HarmonicAngleForce + force1 : mm.HarmonicAngleForce + + """ + + assert type(force0) == type(force1), "Error: force0 and force1 must be the same type." + assert type(force0) == mm.HarmonicAngleForce, "Error: forces must be HarmonicAngleForces" + n_angles0 = force0.getNumAngles() n_angles1 = force1.getNumAngles() dict0, dict1 = {}, {} - + i0, i1, i2, theta0, k0 = force0.getAngleParameters(0) unit_theta = theta0.unit unit_k = k0.unit for k in range(n_angles0): i0, i1, i2, theta0, k0 = force0.getAngleParameters(k) - if (k0 / k0.unit) != 0.0: + if (k0 / k0.unit) != 0.0: # Skip forces with strength 0.0 i0, i1, i2 = reorder_angles(i0, i1, i2) dict0[i0, i1, i2] = ((theta0 / unit_theta, k0 / unit_k)) - + for k in range(n_angles1): i0, i1, i2, theta0, k0 = force1.getAngleParameters(k) - if (k0 / k0.unit) != 0.0: + if (k0 / k0.unit) != 0.0: # Skip forces with strength 0.0 i0, i1, i2 = reorder_angles(i0, i1, i2) dict1[i0, i1, i2] = ((theta0 / unit_theta, k0 / unit_k)) + keys0 = set(dict0.keys()) + keys1 = set(dict1.keys()) + logger.info("Angles0 - Angles1 = %s" % (keys0.difference(keys1))) + logger.info("Angles1 - Angles0 = %s" % (keys1.difference(keys0))) assert set(dict0.keys()) == set(dict1.keys()), "Systems have different HarmonicAngle Forces" for k, parameter_name in enumerate(["theta0", "k0"]): for (i0, i1, i2) in dict0.keys(): val0 = dict0[i0, i1, i2][k] val1 = dict1[i0, i1, i2][k] - assert (abs(val0 - val1) / val0) < EPSILON, "Error: Harmonic Angle (%d, %d, %d) has %s values of %f and %f, respectively." % (i0, i1, i2, parameter_name, val0, val1) + assert compare(val0, val1), "Error: Harmonic Angle (%d, %d, %d) has %s values of %f and %f, respectively." % (i0, i1, i2, parameter_name, val0, val1) def check_nonbonded(self, force0, force1): - + """Check that force0 and force1 are equivalent Nonbonded forces. + + + Parameters + ---------- + force0 : mm.NonbondedForce + force1 : mm.NonbondedForce + + """ + + assert type(force0) == type(force1), "Error: force0 and force1 must be the same type." + assert type(force0) == mm.NonbondedForce, "Error: forces must be NonbondedForces" assert force0.getNumParticles() == force1.getNumParticles(), "Error: Systems have %d and %d particles in NonbondedForce, respectively." % (force0.getNumParticles(), force1.getNumParticles()) - + n_atoms = force0.getNumParticles() - + q, sigma, epsilon = force0.getParticleParameters(0) unit_q, unit_sigma, unit_epsilon = q.unit, sigma.unit, epsilon.unit - + for k in range(n_atoms): q0, sigma0, epsilon0 = force0.getParticleParameters(k) q1, sigma1, epsilon1 = force1.getParticleParameters(k) @@ -195,29 +302,18 @@ def check_nonbonded(self, force0, force1): q0, sigma0, epsilon0 = q0 / unit_q, sigma0 / unit_sigma, epsilon0 / unit_epsilon q1, sigma1, epsilon1 = q1 / unit_q, sigma1 / unit_sigma, epsilon1 / unit_epsilon - if q0 == 0.: - denominator = 1.0 # Don't normalize if has value zero - else: - denominator = q0 # Normalize to compare relative errors, rather than absolute. + assert compare(q0, q1), "Error: Particle %d has charges of %f and %f, respectively." % (k, q0, q1) - assert (abs(q0 - q1) / denominator) < EPSILON, "Error: Particle %d has charges of %f and %f, respectively." % (k, q0, q1) if epsilon0 != 0.: - assert (abs(sigma0 - sigma1) / sigma0) < EPSILON, "Error: Particle %d has sigma of %f and %f, respectively." % (k, sigma0, sigma1) + assert compare(sigma0, sigma1), "Error: Particle %d has sigma of %f and %f, respectively." % (k, sigma0, sigma1) else: logger.info("Skipping comparison of sigma (%f, %f) on particle %d because epsilon has values %f, %f" % (sigma0, sigma1, k, epsilon0, epsilon1)) - if epsilon0 == 0.: - denominator = 1.0 # Don't normalize if has value zero - else: - denominator = epsilon0 # Normalize to compare relative errors, rather than absolute. - - assert (abs(epsilon0 - epsilon1) / denominator) < EPSILON, "Error: Particle %d has epsilon of %f and %f, respectively." % (k, epsilon0, epsilon1) - + assert compare(epsilon0, epsilon1), "Error: Particle %d has epsilon of %f and %f, respectively." % (k, epsilon0, epsilon1) n_exceptions = force0.getNumExceptions() assert force0.getNumExceptions() == force1.getNumExceptions(), "Error: Systems have %d and %d exceptions in NonbondedForce, respectively." % (force0.getNumExceptions(), force1.getNumExceptions()) - i0, i1, qq, sigma, epsilon = force0.getExceptionParameters(0) unit_qq, unit_sigma, unit_epsilon = qq.unit, sigma.unit, epsilon.unit @@ -230,25 +326,47 @@ def check_nonbonded(self, force0, force1): i0, i1, qq, sigma, epsilon = force1.getExceptionParameters(k) i0, i1 = reorder_bonds(i0, i1) dict1[i0, i1] = ((qq / unit_qq, sigma / unit_sigma, epsilon / unit_epsilon)) - + + keys0 = set(dict0.keys()) + keys1 = set(dict1.keys()) + logger.info("Exceptions0 - Exceptions1 = %s" % (keys0.difference(keys1))) + logger.info("Exceptions1 - Exceptions0 = %s" % (keys1.difference(keys0))) assert set(dict0.keys()) == set(dict1.keys()), "Systems have different NonBondedForce Exceptions" for k, parameter_name in enumerate(["qq", "sigma", "epsilon"]): for (i0, i1) in dict0.keys(): val0 = dict0[i0, i1][k] val1 = dict1[i0, i1][k] - denominator = abs(val0) - if denominator < EPSILON: - denominator = 1.0 if parameter_name == "sigma" and dict0[i0, i1][2] == 0.0 and dict1[i0, i1][2] == 0.0: - continue # If both epsilon parameters are zero, then sigma doesn't matter so skip the comparison. - assert (abs(val0 - val1) / denominator) < EPSILON, "Error: NonBondedForce Exception (%d, %d) has %s values of %f and %f, respectively." % (i0, i1, parameter_name, val0, val1) + continue # If both epsilon parameters are zero, then sigma doesn't matter so skip the comparison. + assert compare(val0, val1), "Error: NonBondedForce Exception (%d, %d) has %s values of %f and %f, respectively." % (i0, i1, parameter_name, val0, val1) def check_proper_torsions(self, force0, force1, bond_force0, bond_force1): - + """Check that force0 and force1 are equivalent PeriodicTorsion forces. + + + Parameters + ---------- + force0 : mm.PeriodicTorsionForce + force1 : mm.PeriodicTorsionForce + + Notes + ----- + + So this creates and compares a pair of dictionaries, one for each + force. Each dictionary has keys that are atom tuples (i0,i1,i2,i3) + and values which are a list of force parameters (per, phase, k0) + for that tuple. This complexity is required because a given tuple + can have *multiple* parameters associated with it. + + """ + + assert type(force0) == type(force1), "Error: force0 and force1 must be the same type." + assert type(force0) == mm.PeriodicTorsionForce, "Error: forces must be PeriodicTorsionForce" + bond_set0 = get_symmetrized_bond_set(bond_force0) bond_set1 = get_symmetrized_bond_set(bond_force1) - + i0, i1, i2, i3, per, phase, k0 = force0.getTorsionParameters(0) phase_unit, k0_unit = phase.unit, k0.unit @@ -264,7 +382,7 @@ def check_proper_torsions(self, force0, force1, bond_force0, bond_force1): if k0 == 0.0: continue - if not dict0.has_key((i0, i1, i2, i3)): + if not (i0, i1, i2, i3) in dict0: dict0[i0, i1, i2, i3] = [] dict0[i0, i1, i2, i3].append((per, phase, k0)) @@ -280,7 +398,7 @@ def check_proper_torsions(self, force0, force1, bond_force0, bond_force1): if k0 == 0.0: continue - if not dict1.has_key((i0, i1, i2, i3)): + if not (i0, i1, i2, i3) in dict1: dict1[i0, i1, i2, i3] = [] dict1[i0, i1, i2, i3].append((per, phase, k0)) @@ -289,28 +407,65 @@ def check_proper_torsions(self, force0, force1, bond_force0, bond_force1): keys1 = set(dict1.keys()) diff_keys = keys0.symmetric_difference(keys1) + logger.info("Propers0 - Propers1 = %s" % (keys0.difference(keys1))) + logger.info("Propers1 - Propers0 = %s" % (keys1.difference(keys0))) assert diff_keys == set(), "Systems have different (proper) PeriodicTorsionForce entries: extra keys are: \n%s" % diff_keys for (i0, i1, i2, i3) in dict0.keys(): entries0 = dict0[i0, i1, i2, i3] - entries1 = dict1[i0, i1, i2, i3] + entries1 = dict1[i0, i1, i2, i3] assert len(entries0) == len(entries1), "Error: (proper) PeriodicTorsionForce entry (%d, %d, %d, %d) has different numbers of terms (%d and %d, respectively)." % (i0, i1, i2, i3, len(entries0), len(entries1)) - + subdict0 = dict(((per, reduce_precision(phase)), k0) for (per, phase, k0) in entries0) subdict1 = dict(((per, reduce_precision(phase)), k0) for (per, phase, k0) in entries1) - + assert set(subdict0.keys()) == set(subdict1.keys()), "Error: (proper) PeriodicTorsionForce entry (%d, %d, %d, %d) has different terms." % (i0, i1, i2, i3) - + for (per, phase) in subdict0.keys(): - val0 = subdict0[per, phase] + val0 = subdict0[per, phase] val1 = subdict1[per, phase] - assert (abs(val0 - val1) / val0) < EPSILON, "Error: (proper) PeriodicTorsionForce strength (%d, %d, %d, %d) (%d, %f) has values of %f and %f, respectively." % (i0, i1, i2, i3, per, phase, val0, val1) + assert compare(val0, val1), "Error: (proper) PeriodicTorsionForce strength (%d, %d, %d, %d) (%d, %f) has values of %f and %f, respectively." % (i0, i1, i2, i3, per, phase, val0, val1) + + def check_improper_torsions(self, force0, force1, bond_force0, bond_force1): + """Check that force0 and force1 are equivalent PeriodicTorsion forces. + + + Parameters + ---------- + force0 : mm.PeriodicTorsionForce + force1 : mm.PeriodicTorsionForce + + Notes + ----- + + So this creates and compares a pair of dictionaries, one for each + force. Each dictionary has keys that are atom tuples (i0,i1,i2,i3) + and values which are a list of force parameters (per, phase, k0) + for that tuple. This complexity is required because a given tuple + can have *multiple* parameters associated with it. + + In addition to the complications for *all* torsions, improper torsions + have the additional difficulty of ambiguous definitions. Given a tuple + (a,b,c,d) that is held planar by an improper, one can define several permuted dihedral + terms that give similar (but not identical) energies. Many force field + codes do NOT pay attention to the exact permutation that is used, leading to + subtle differences in topology and energy between MD packages. + + The solution that we use here is to reorder each improper torsion via: + + central_atom, i1, i2, i3 + + where i1, i2, and i3 are in increasing order and the central atom + is located via the adjacency matrix of bonds. + + """ + + assert type(force0) == type(force1), "Error: force0 and force1 must be the same type." + assert type(force0) == mm.PeriodicTorsionForce, "Error: forces must be PeriodicTorsionForce" - def check_improper_torsions(self, force0, force1, bond_force0, bond_force1): - bond_set0 = get_symmetrized_bond_set(bond_force0) bond_set1 = get_symmetrized_bond_set(bond_force1) - + i0, i1, i2, i3, per, phase, k0 = force0.getTorsionParameters(0) phase_unit, k0_unit = phase.unit, k0.unit @@ -326,7 +481,7 @@ def check_improper_torsions(self, force0, force1, bond_force0, bond_force1): if k0 == 0.0: continue - if not dict0.has_key((i0, i1, i2, i3)): + if not (i0, i1, i2, i3) in dict0: dict0[i0, i1, i2, i3] = [] dict0[i0, i1, i2, i3].append((per, phase, k0)) @@ -342,7 +497,7 @@ def check_improper_torsions(self, force0, force1, bond_force0, bond_force1): if k0 == 0.0: continue - if not dict1.has_key((i0, i1, i2, i3)): + if not (i0, i1, i2, i3) in dict1: dict1[i0, i1, i2, i3] = [] dict1[i0, i1, i2, i3].append((per, phase, k0)) @@ -351,37 +506,44 @@ def check_improper_torsions(self, force0, force1, bond_force0, bond_force1): keys1 = set(dict1.keys()) diff_keys = keys0.symmetric_difference(keys1) - logger.info("Torsions0 - Torsions1 = %s" % (keys0.difference(keys1))) - logger.info("Torsions1 - Torsions0 = %s" % (keys1.difference(keys0))) + logger.info("Impropers0 - Impropers1 = %s" % (keys0.difference(keys1))) + logger.info("Impropers1 - Impropers0 = %s" % (keys1.difference(keys0))) assert diff_keys == set(), "Systems have different (improper) PeriodicTorsionForce entries: extra keys are: \n%s" % diff_keys for (i0, i1, i2, i3) in dict0.keys(): entries0 = dict0[i0, i1, i2, i3] - entries1 = dict1[i0, i1, i2, i3] + entries1 = dict1[i0, i1, i2, i3] assert len(entries0) == len(entries1), "Error: (improper) PeriodicTorsionForce entry (%d, %d, %d, %d) has different numbers of terms (%d and %d, respectively)." % (i0, i1, i2, i3, len(entries0), len(entries1)) - + subdict0 = dict(((per, reduce_precision(phase)), k0) for (per, phase, k0) in entries0) subdict1 = dict(((per, reduce_precision(phase)), k0) for (per, phase, k0) in entries1) - + assert set(subdict0.keys()) == set(subdict1.keys()), "Error: (improper) PeriodicTorsionForce entry (%d, %d, %d, %d) has different terms." % (i0, i1, i2, i3) - + for (per, phase) in subdict0.keys(): - val0 = subdict0[per, phase] + val0 = subdict0[per, phase] val1 = subdict1[per, phase] - assert (abs(val0 - val1) / val0) < EPSILON, "Error: (improper) PeriodicTorsionForce strength (%d, %d, %d, %d) (%d, %f) has values of %f and %f, respectively." % (i0, i1, i2, i3, per, phase, val0, val1) + assert compare(val0, val1), "Error: (improper) PeriodicTorsionForce strength (%d, %d, %d, %d) (%d, %f) has values of %f and %f, respectively." % (i0, i1, i2, i3, per, phase, val0, val1) def zero_degenerate_impropers(self, f): - """Set the force constant to zero for improper dihedrals that + """Set the force constant to zero for improper dihedrals that involve less than four unique atoms. """ for k in range(f.getNumTorsions()): i0, i1, i2, i3, per, phase, k0 = f.getTorsionParameters(k) if len(set([i0, i1, i2, i3])) < 4: f.setTorsionParameters(k, i0, i1, i2, i3, per, phase, k0 * 0.0) - def check_energies(self, zero_degenerate_impropers=True): - if zero_degenerate_impropers == True: + """Compare the energies of the two simulations. + + Parameters + ---------- + + zero_degenerate_impropers : bool, default=True + if True, zero out all impropers with < 4 atoms. + """ + if zero_degenerate_impropers is True: self.zero_degenerate_impropers(self.torsion_force0) xyz = self.simulation0.context.getState(getPositions=True).getPositions() self.simulation0.context.reinitialize() @@ -396,5 +558,5 @@ def check_energies(self, zero_degenerate_impropers=True): state1 = self.simulation1.context.getState(getEnergy=True) energy1 = state1.getPotentialEnergy() - + return energy0, energy1 diff --git a/gaff2xml/utils.py b/gaff2xml/utils.py new file mode 100644 index 0000000..7cfba20 --- /dev/null +++ b/gaff2xml/utils.py @@ -0,0 +1,326 @@ +import os +import os.path +import tempfile +import logging +from pkg_resources import resource_filename +import contextlib +import shutil + +try: + from subprocess import getoutput # If python 3 +except ImportError: + from commands import getoutput # If python 2 + +import openeye.oechem + +import simtk.openmm +from simtk.openmm import app +import simtk.unit as units + +from gaff2xml import amber_parser, gafftools, system_checker + +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.DEBUG, format="LOG: %(message)s") + +AMBERHOME = os.environ['AMBERHOME'] +GAFF_DAT_FILENAME = os.path.join(AMBERHOME, 'dat', 'leap', 'parm', 'gaff.dat') + + +@contextlib.contextmanager +def enter_temp_directory(): + temp_dir = tempfile.mkdtemp() + cwd = os.getcwd() + os.chdir(temp_dir) + yield + os.chdir(cwd) + shutil.rmtree(temp_dir) + + +def parse_ligand_filename(filename): + """Split ligand filename into name and extension. "./ligand.mol2" -> ("ligand", ".mol2")""" + name, ext = os.path.splitext(os.path.split(filename)[1]) + return name, ext + + +def run_antechamber(molecule_name, input_filename, charge_method=None): + """Run AmberTools antechamber and parmchk to create GAFF mol2 and frcmod files. + + Parameters + ---------- + molecule_name : str + Name of the molecule to be parameterized, will be used in output filenames. + ligand_filename : str + The molecule to be parameterized. Must be tripos mol2 format. + charge_method : str, optional + If not None, the charge method string will be passed to Antechamber. + + Returns + ------- + gaff_mol2_filename : str + GAFF format mol2 filename produced by antechamber + frcmod_filename : str + Amber frcmod file produced by prmchk + """ + + ext = parse_ligand_filename(input_filename)[1] + + filetype = ext[1:] + if filetype != "mol2": + raise(ValueError("Must input mol2 filename")) + + gaff_mol2_filename = molecule_name + '.gaff.mol2' + frcmod_filename = molecule_name + '.frcmod' + + cmd = "antechamber -i %s -fi mol2 -o %s -fo mol2 -s 2" % (input_filename, gaff_mol2_filename) + if charge_method is not None: + cmd += ' -c %s' % charge_method + + logger.debug(cmd) + + output = getoutput(cmd) + logger.debug(output) + + cmd = "parmchk -i %s -f mol2 -o %s" % (gaff_mol2_filename, frcmod_filename) + logger.debug(cmd) + + output = getoutput(cmd) + logger.debug(output) + + return gaff_mol2_filename, frcmod_filename + + +def convert_molecule(in_filename, out_filename): + """Use openbabel to convert filenames. May not work for all file formats!""" + + molecule_name, ext_in = parse_ligand_filename(in_filename) + molecule_name, ext_out = parse_ligand_filename(out_filename) + + cmd = "obabel -i %s %s -o %s > %s" % (ext_in, in_filename, ext_out, out_filename) + + output = getoutput(cmd) + logger.debug(output) + + +def run_tleap(molecule_name, gaff_mol2_filename, frcmod_filename): + """Run AmberTools tleap to create simulation files for AMBER + + Parameters + ---------- + gaff_mol2_filename : str + GAFF format mol2 filename produced by antechamber + frcmod_filename : str + Amber frcmod file produced by prmchk + + Returns + ------- + gaff_mol2_filename : str + GAFF format mol2 filename produced by antechamber + frcmod_filename : str + Amber frcmod file produced by prmchk + """ + + prmtop_filename = "%s.prmtop" % molecule_name + inpcrd_filename = "%s.inpcrd" % molecule_name + + tleap_input = """ +source leaprc.ff99SB +source leaprc.gaff +LIG = loadmol2 %s +check LIG +loadamberparams %s +saveamberparm LIG %s %s +quit + +""" % (gaff_mol2_filename, frcmod_filename, prmtop_filename, inpcrd_filename) + + file_handle = tempfile.NamedTemporaryFile() + file_handle.writelines(tleap_input) + file_handle.flush() + + cmd = "tleap -f %s " % file_handle.name + logger.debug(cmd) + + output = getoutput(cmd) + logger.debug(output) + + file_handle.close() + + return prmtop_filename, inpcrd_filename + + +def molecule_to_mol2(molecule, tripos_mol2_filename=None): + """Convert OE molecule to tripos mol2 file. + + Parameters + ---------- + molecule : openeye.oechem.OEGraphMol + The molecule to be converted. + + Returns + ------- + tripos_mol2_filename : str + Filename of output tripos mol2 file + """ + # Get molecule name. + molecule_name = molecule.GetTitle() + logger.debug(molecule_name) + + # Write molecule as Tripos mol2. + if tripos_mol2_filename is None: + tripos_mol2_filename = molecule_name + '.tripos.mol2' + + ofs = openeye.oechem.oemolostream(tripos_mol2_filename) + ofs.SetFormat(openeye.oechem.OEFormat_MOL2H) + openeye.oechem.OEWriteMolecule(ofs, molecule) + ofs.close() + + # Replace <0> substructure names with valid text. + infile = open(tripos_mol2_filename, 'r') + lines = infile.readlines() + infile.close() + newlines = [line.replace('<0>', 'MOL') for line in lines] + outfile = open(tripos_mol2_filename, 'w') + outfile.writelines(newlines) + outfile.close() + + return molecule_name, tripos_mol2_filename + + +def create_ffxml_simulation(molecule_name, gaff_mol2_filename, frcmod_filename): + """Process a gaff mol2 file and frcmod file using the XML conversion, returning an OpenMM simulation. + + Parameters + ---------- + molecule_name : str + The name of the molecule + gaff_mol2_filename : str + The name of the gaff mol2 file + frcmod_filename : str + The name of the gaff frcmod file + + Returns + ------- + simulation : openmm.app.Simulation + A functional simulation object for simulating your molecule + """ + + # Generate ffxml file. + parser = amber_parser.AmberParser() + parser.parse_filenames([GAFF_DAT_FILENAME, gaff_mol2_filename, frcmod_filename]) + + ffxml_stream = parser.generate_xml() + ffxml_filename = molecule_name + '.ffxml' + outfile = open(ffxml_filename, 'w') + outfile.write(ffxml_stream.read()) + outfile.close() + + mol2 = gafftools.Mol2Parser(gaff_mol2_filename) # Read mol2 file. + (topology, positions) = mol2.to_openmm() + + # Create System object. + forcefield = app.ForceField(ffxml_filename) + system = forcefield.createSystem(topology, nonbondedMethod=app.NoCutoff, constraints=None, implicitSolvent=None) + + # Create integrator. + timestep = 1.0 * units.femtoseconds + integrator = simtk.openmm.VerletIntegrator(timestep) + + # Create simulation. + platform = simtk.openmm.Platform.getPlatformByName("Reference") + simulation = app.Simulation(topology, system, integrator, platform=platform) + simulation.context.setPositions(positions) + + return simulation + + +def create_leap_simulation(molecule_name, gaff_mol2_filename, frcmod_filename): + """Create an OpenMM simulation using a Gaff mol2 file and frcmod file. + + + Parameters + ---------- + molecule_name : str + Name of the molecule + gaff_mol2_filename : str + Filename of input (GAFF!) mol2 file + frcmod_filename : str + Use this frcmod filename + + """ + + # Parameterize system with LEaP. + (prmtop_filename, inpcrd_filename) = run_tleap(molecule_name, gaff_mol2_filename, frcmod_filename) + + # Create System object. + prmtop = app.AmberPrmtopFile(prmtop_filename) + topology = prmtop.topology + system = prmtop.createSystem(nonbondedMethod=app.NoCutoff, constraints=None, implicitSolvent=None) + + # Read positions. + inpcrd = app.AmberInpcrdFile(inpcrd_filename) + positions = inpcrd.getPositions() + + # Create integrator. + timestep = 1.0 * units.femtoseconds + integrator = simtk.openmm.VerletIntegrator(timestep) + + platform = simtk.openmm.Platform.getPlatformByName("Reference") + simulation = app.Simulation(topology, system, integrator, platform=platform) + simulation.context.setPositions(positions) + + return simulation + + +def test_molecule(molecule_name, tripos_mol2_filename, charge_method=None, energy_epsilon=0.5): + """Create a GAFF molecule via LEAP and ffXML and compare force terms. + + + Parameters + ---------- + molecule_name : str + Name of the molecule + tripos_mol2_filename : str + Filename of input mol2 file + charge_method : str, default=None + If None, use charges in existing MOL2. Otherwise, use a charge + model when running antechamber. + energy_epsilon : float, default=0.5 (units assumed to be KJ / mol) + Raise error if energy difference is above this value. + """ + + # Generate GAFF parameters. + (gaff_mol2_filename, frcmod_filename) = run_antechamber(molecule_name, tripos_mol2_filename, charge_method=charge_method) + + # Create simulations. + simulation_ffxml = create_ffxml_simulation(molecule_name, gaff_mol2_filename, frcmod_filename) + simulation_leap = create_leap_simulation(molecule_name, gaff_mol2_filename, frcmod_filename) + + # Compare simulations. + syscheck = system_checker.SystemChecker(simulation_ffxml, simulation_leap) + syscheck.check_force_parameters() + + energy0, energy1 = syscheck.check_energies() + delta = abs((energy0 - energy1) / units.kilojoules_per_mole) + assert delta < energy_epsilon, "Error, energy difference (%f) is greater than %f" % (delta, energy_epsilon) + + +def get_data_filename(relative_path): + """Get the full path to one of the reference files shipped for testing + + In the source distribution, these files are in ``gaff2xml/chemicals/*/``, + but on installation, they're moved to somewhere in the user's python + site-packages directory. + + Parameters + ---------- + name : str + Name of the file to load (with respect to the gaff2xml folder). + + """ + + fn = resource_filename('gaff2xml', relative_path) + + if not os.path.exists(fn): + raise ValueError("Sorry! %s does not exist. If you just added it, you'll have to re-install" % fn) + + return fn diff --git a/scripts/generate_example_data.py b/scripts/generate_example_data.py index 9b6ea48..5fbd978 100755 --- a/scripts/generate_example_data.py +++ b/scripts/generate_example_data.py @@ -1,38 +1,6 @@ #!/usr/bin/env python -import os -import tempfile import sys - -AMBER_PATH = "~/src/amber12/dat/leap/parm/" - -def run_antechamber(ligand_name): - cmd = "obabel -i sdf %s.sdf -o pdb > %s.pdb" % (ligand_name, ligand_name) - os.system(cmd) - cmd = "antechamber -i %s.pdb -fi pdb -o %s.mol2 -fo mol2 -c bcc -s 2" % (ligand_name, ligand_name) - os.system(cmd) - cmd = "parmchk -i %s.mol2 -f mol2 -o %s.frcmod" % (ligand_name, ligand_name) - os.system(cmd) - cmd = "processAmberForceField.py %s/gaff.dat ./%s.mol2 ./%s.frcmod > %s.xml" % (AMBER_PATH, ligand_name, ligand_name, ligand_name) - os.system(cmd) - - tleap_input = """ -source leaprc.ff99SB -source leaprc.gaff -LIG = loadmol2 %s.mol2 -check LIG -loadamberparams %s.frcmod -saveoff LIG %s.lib -saveamberparm LIG %s.prmtop %s.inpcrd -quit - -""" % (ligand_name, ligand_name, ligand_name, ligand_name, ligand_name) - - file_handle = tempfile.NamedTemporaryFile() - file_handle.writelines(tleap_input) - file_handle.flush() - - cmd = "tleap -f %s " % file_handle.name - os.system(cmd) +from gaff2xml.utils import run_antechamber if __name__ == "__main__": if len(sys.argv) <= 1: @@ -40,4 +8,5 @@ def run_antechamber(ligand_name): Note: this should be run in the gaff2xml/chemicals/ligand_name directory. """) else: - run_antechamber(sys.argv[1]) + molecule_name, mol2_filename = sys.argv[1:] + run_antechamber(molecule_name, mol2_filename, charge_model="bcc") diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..37a0ad8 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[nosetests] +verbosity=2 +with-doctest=1 diff --git a/setup.py b/setup.py index 597993c..7e498e0 100644 --- a/setup.py +++ b/setup.py @@ -68,6 +68,6 @@ zip_safe=False, scripts=['scripts/generate_example_data.py', 'scripts/processAmberForceField.py'], ext_modules=extensions, - #package_data={'mdtraj.pdb': ['data/*'], 'mdtraj.testing': ['reference/*']}, + package_data={'gaff2xml': ['chemicals/*/*']}, # Install all data directories of the form testsystems/data/X/ **setup_kwargs ) diff --git a/tests/test_drugs.py b/tests/test_drugs.py new file mode 100644 index 0000000..5dbdf34 --- /dev/null +++ b/tests/test_drugs.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +from unittest import skipIf + +import openeye.oechem + +from gaff2xml import utils + +#@skipIf(os.environ.get("TRAVIS", None) == 'true', "Skip testing of entire drug database on Travis.") +@skipIf(True, "Skipping drugs.") +def test_drugs(): + database_filename = utils.get_data_filename("chemicals/drugs/Zdd.mol2.gz") + ifs = openeye.oechem.oemolistream(database_filename) + for molecule in ifs.GetOEGraphMols(): + with utils.enter_temp_directory(): + molecule_name, tripos_mol2_filename = utils.molecule_to_mol2(molecule) + yield lambda : utils.test_molecule(molecule_name, tripos_mol2_filename) # Cute trick to iteratively run this test over entire database. + + +def test_drug(): + + database_filename = utils.get_data_filename("chemicals/drugs/Zdd.mol2.gz") + ifs = openeye.oechem.oemolistream(database_filename) + for molecule in ifs.GetOEGraphMols(): + with utils.enter_temp_directory(): + molecule_name, tripos_mol2_filename = utils.molecule_to_mol2(molecule) + yield lambda : utils.test_molecule(molecule_name, tripos_mol2_filename) # Cute trick to iteratively run this test over entire database. + break diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..27ae64b --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python + +from unittest import skipIf +import logging + +logging.basicConfig(level=logging.DEBUG, format="LOG: %(message)s") + +import openeye.oechem +from mdtraj.testing import eq +from gaff2xml import utils + +def test_enter_temp_directory(): + with utils.enter_temp_directory(): + pass + +def test_parse_ligand_filename(): + molecule_name = "sustiva" + input_filename = utils.get_data_filename("chemicals/sustiva/sustiva.mol2") + name, ext = utils.parse_ligand_filename(input_filename) + + eq(name, "sustiva") + eq(ext, ".mol2") + +def test_run_antechamber(): + molecule_name = "sustiva" + input_filename = utils.get_data_filename("chemicals/sustiva/sustiva.mol2") + with utils.enter_temp_directory(): # Prevents creating tons of GAFF files everywhere. + gaff_mol2_filename, frcmod_filename = utils.run_antechamber(molecule_name, input_filename, charge_method=None) + +def test_run_tleap(): + molecule_name = "sustiva" + input_filename = utils.get_data_filename("chemicals/sustiva/sustiva.mol2") + with utils.enter_temp_directory(): # Prevents creating tons of GAFF files everywhere. + gaff_mol2_filename, frcmod_filename = utils.run_antechamber(molecule_name, input_filename, charge_method=None) + prmtop, inpcrd = utils.run_tleap(molecule_name, gaff_mol2_filename, frcmod_filename) + +def test_run_test_molecule(): + molecule_name = "sustiva" + input_filename = utils.get_data_filename("chemicals/sustiva/sustiva.mol2") + with utils.enter_temp_directory(): # Prevents creating tons of GAFF files everywhere. + utils.test_molecule(molecule_name, input_filename) diff --git a/tools/ci/apt_install.sh b/tools/ci/apt_install.sh new file mode 100755 index 0000000..a132307 --- /dev/null +++ b/tools/ci/apt_install.sh @@ -0,0 +1,8 @@ +sudo apt-get install python-software-properties git -y +sudo apt-add-repository ppa:fkrull/deadsnakes -y +sudo apt-get update + +sudo apt-get install libfreetype6-dev libpng12-dev libhdf5-serial-dev \ + g++ libatlas-base-dev gfortran netcdf-bin libnetcdf-dev libffi-dev -y +sudo apt-get install ${python}-dev + diff --git a/tools/ci/py_install.sh b/tools/ci/py_install.sh new file mode 100755 index 0000000..61d8387 --- /dev/null +++ b/tools/ci/py_install.sh @@ -0,0 +1,27 @@ +set -x + +$python -V + +# Remove any old distribute or setuptools +# sudo rm -rf `$python -c 'import distutils.sysconfig; print(distutils.sysconfig.get_python_lib())'`/setuptools* +# sudo rm -rf `$python -c 'import distutils.sysconfig; print(distutils.sysconfig.get_python_lib())'`/distribute* + +# Get a new setuptools +wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | sudo $python +# Get a bootstrap pip +sudo easy_install pip + +# Install Dependencies +# as of pip 1.4rc2, wheel files are still being broken regularly, this is a +# known good commit. should revert to pypi when a final release is out +pip_commit=42102e9deaea99db08b681d06906c2945f6f95e2 +PYTHON_VERSION=`$python -c 'import sys; print("%d.%d" % sys.version_info[:2])'` +sudo pip install -I git+https://github.com/pypa/pip@$pip_commit#egg=pip +sudo pip install -I -U wheel + +base_url=http://s3.amazonaws.com/mdtraj-deps-wheelhouse +PIP_ARGS=" -I --use-wheel --find-links=$base_url/${PYTHON_VERSION}/index.html" +time sudo pip install $PIP_ARGS -r tools/ci/requirements-${PYTHON_VERSION}.txt + +# TODO: move to requirements +sudo pip install coverage diff --git a/tools/ci/requirements.txt b/tools/ci/requirements.txt new file mode 100644 index 0000000..3bdf2d7 --- /dev/null +++ b/tools/ci/requirements.txt @@ -0,0 +1,11 @@ +numpy==1.7.1 +scipy==0.12.0 +cython==0.19.1 +pandas==0.12.0 +pyflakes==0.7.3 +scripttest==1.3 +git+git://github.com/rmcgibbo/simtk.unit +nose==1.3.0 +nose-exclude==0.1.10 +coverage +coveralls