def test_label_molecules(self): """Test labeling/getting stats on labeling molecules""" molecules = read_molecules( get_data_file_path('molecules/AlkEthOH_test_filt1_tripos.mol2'), verbose=verbose) ffxml = get_data_file_path('test_forcefields/Frosst_AlkEthOH.offxml') get_molecule_parameterIDs(molecules, ffxml)
def test_change_parameters(verbose=False): """Test modification of forcefield parameters.""" from openeye import oechem # Load simple OEMol ifs = oechem.oemolistream(get_data_file_path('molecules/AlkEthOH_c100.mol2')) mol = oechem.OEMol() flavor = oechem.OEIFlavor_Generic_Default | oechem.OEIFlavor_MOL2_Default | oechem.OEIFlavor_MOL2_Forcefield ifs.SetFlavor(oechem.OEFormat_MOL2, flavor) oechem.OEReadMolecule(ifs, mol) oechem.OETriposAtomNames(mol) # Load forcefield file ff = ForceField('Frosst_AlkEthOH.offxml') topology = generateTopologyFromOEMol(mol) # Create initial system system = ff.createSystem(topology, [mol], verbose=verbose) # Get initial energy before parameter modification positions = positions_from_oemol(mol) old_energy = get_energy(system, positions) # Get params for an angle params = ff.getParameter(smirks='[a,A:1]-[#6X4:2]-[a,A:3]') # Modify params params['k'] = '0.0' ff.setParameter(params, smirks='[a,A:1]-[#6X4:2]-[a,A:3]') # Write params ff.writeFile(tempfile.TemporaryFile(suffix='.offxml')) # Make sure params changed energy! (Test whether they get rebuilt on system creation) system = ff.createSystem(topology, [mol], verbose=verbose) energy = get_energy(system, positions) if verbose: print("New energy/old energy:", energy, old_energy) if np.abs(energy - old_energy) < 0.1: raise Exception("Error: Parameter modification did not change energy.")
def read_typelist(file_path): """ Read a parameter type or decorator list from a file. Lines in these files have the format "SMARTS/SMIRKS shorthand" lines beginning with '%' are ignored Parameters ---------- file_path : str Path and name of file to be read Could be file in openforcefield/data/ Returns ------- typelist : list of tuples Typelist[i] is element i of the typelist in format (smarts, shorthand) """ warnings.warn(DEPRECATION_WARNING_TEXT, PendingDeprecationWarning) from openforcefield.utils import get_data_file_path if file_path is None: return None if not os.path.exists(file_path): built_in = get_data_file_path(file_path) if not os.path.exists(built_in): raise Exception(f"File '{file_path}' not found.") file_path = built_in typelist = list() ifs = open(file_path) lines = ifs.readlines() used_typenames = list() for line in lines: # Strip trailing comments index = line.find('%') if index != -1: line = line[0:index] # Split into tokens. tokens = line.split() # Process if we have enough tokens if len(tokens) >= 2: smarts = tokens[0] typename = ' '.join(tokens[1:]) if typename not in used_typenames: typelist.append([smarts, typename]) used_typenames.append(typename) else: raise Exception( f"Error in file '{file_path}' -- each entry must have a unique name." ) ifs.close() return typelist
def test_get_mol2_gaff_atom_types(self): """Test that a warning is raised OpenEyeToolkitWrapper when it detects GAFF atom types in a mol2 file.""" toolkit_wrapper = OpenEyeToolkitWrapper() mol2_file_path = get_data_file_path( 'molecules/AlkEthOH_test_filt1_ff.mol2') with pytest.warns(GAFFAtomTypeWarning, match='SYBYL'): Molecule.from_file(mol2_file_path, toolkit_registry=toolkit_wrapper)
def test_get_pdb_coordinates(self): """Test RDKitToolkitWrapper for importing a single set of coordinates from a pdb file""" toolkit_wrapper = RDKitToolkitWrapper() filename = get_data_file_path('molecules/toluene.pdb') molecule = Molecule.from_file(filename, toolkit_registry=toolkit_wrapper) assert len(molecule._conformers) == 1 assert molecule._conformers[0].shape == (15, 3)
def test_load_aromatic_pdb(self): """Test OpenEyeToolkitWrapper for importing molecule conformers""" toolkit_wrapper = RDKitToolkitWrapper() filename = get_data_file_path('molecules/toluene.pdb') molecule = Molecule.from_file(filename, toolkit_registry=toolkit_wrapper) assert len(molecule._conformers) == 1 assert molecule._conformers[0].shape == (15, 3)
def test_get_multiconformer_sdf_coordinates(self): """Test RDKitToolkitWrapper for importing a single set of coordinates from a sdf file""" raise NotImplementedError toolkit_wrapper = RDKitToolkitWrapper() filename = get_data_file_path('molecules/toluene.sdf') molecule = Molecule.from_file(filename, toolkit_registry=toolkit_wrapper) assert len(molecule._conformers) == 1 assert molecule._conformers[0].shape == (15, 3)
def setup_class(cls): cls.thing = Thing('blorb', [1, 2, 3]) # Create an example object holding the SMIRNOFF xmltodict dictionary representation import xmltodict from openforcefield.utils import get_data_file_path filename = get_data_file_path('forcefield/smirnoff99Frosst.offxml') with open(filename, 'r') as f: xml = f.read() dictionary = xmltodict.parse(xml) cls.thing = DictionaryContainer(dictionary)
def test_get_sdf_coordinates(self): """Test RDKitToolkitWrapper for importing a single set of coordinates from a sdf file""" toolkit_wrapper = RDKitToolkitWrapper() filename = get_data_file_path('molecules/toluene.sdf') molecule = Molecule.from_file(filename, toolkit_registry=toolkit_wrapper) assert len(molecule._conformers) == 1 assert molecule._conformers[0].shape == (15, 3) assert_almost_equal(molecule.conformers[0][5][1] / unit.angstrom, 2.0104, decimal=4)
def test_improper(verbose=False): """Test implement of impropers on benzene.""" # Create benzene molecule molecule = Molecule.from_iupac('benzene') topology = molecule.to_topology() # Load forcefield ff = ForceField('benzene_minimal.offxml') # Load AMBER files and compare inpcrd = get_data_file_path('molecules/benzene.crd') prmtop = get_data_file_path('molecules/benzene.top') g0, g1, e0, e1 = compare_amber_smirnoff(prmtop, inpcrd, ff, mol, skip_assert=True) # Check that torsional energies the same to 1 in 10^6 rel_error = np.abs((g0['torsion'] - g1['torsion']) / g0['torsion']) if rel_error > 6e-3: #Note that this will not be tiny because we use three-fold impropers and they use a single improper raise Exception( "Improper torsion energy for benzene differs too much (relative error %.4g) between AMBER and SMIRNOFF." % rel_error)
def run_script_file(file_path): """Run through the shell a python script.""" cmd = ['python', file_path] if 'conformer_energies.py' in file_path: cmd.append('--filename') mol_file = get_data_file_path('molecules/ruxolitinib_conformers.sdf') cmd.append(mol_file) try: subprocess.check_call(cmd) except subprocess.CalledProcessError: raise Exception( 'Example {file_path} failed'.format(file_path=file_path))
def run_script_file(file_path): """Run through the shell a python script.""" cmd = ["python", file_path] if "conformer_energies.py" in file_path: cmd.append("--filename") mol_file = get_data_file_path("molecules/ruxolitinib_conformers.sdf") cmd.append(mol_file) try: subprocess.check_call(cmd) except subprocess.CalledProcessError: raise Exception( "Example {file_path} failed".format(file_path=file_path))
def run_script_file(file_path): """Run through the shell a python script.""" with tempfile.TemporaryDirectory() as tmp_dir: with temporary_cd(tmp_dir): cmd = ["python", file_path] if "conformer_energies.py" in file_path: cmd.append("--filename") mol_file = get_data_file_path( "molecules/ruxolitinib_conformers.sdf") cmd.append(mol_file) try: subprocess.check_call(cmd) except subprocess.CalledProcessError: raise Exception(f"Example {file_path} failed")
def read_molecules(file_path, verbose=True): """ Read molecules from an OpenEye-supported file. Parameters ---------- file_path : str Filename from which molecules are to be read (e.g. mol2, sdf) Returns ------- molecules : list of OEMol List of molecules read from file """ warnings.warn(DEPRECATION_WARNING_TEXT, PendingDeprecationWarning) from openeye import oechem from openforcefield.utils import get_data_file_path if not os.path.exists(file_path): built_in = get_data_file_path(f"molecules/{file_path}") if not os.path.exists(built_in): raise Exception(f"File '{file_path}' not found.") file_path = built_in if verbose: print(f"Loading molecules from '{file_path}'...") start_time = time.time() molecules = list() input_molstream = oechem.oemolistream(file_path) flavor = oechem.OEIFlavor_Generic_Default | oechem.OEIFlavor_MOL2_Default | oechem.OEIFlavor_MOL2_Forcefield input_molstream.SetFlavor(oechem.OEFormat_MOL2, flavor) molecule = oechem.OECreateOEGraphMol() while oechem.OEReadMolecule(input_molstream, molecule): # If molecule has no title, try getting SD 'name' tag if molecule.GetTitle() == '': name = oechem.OEGetSDData(molecule, 'name').strip() molecule.SetTitle(name) # Append to list. molecule_copy = oechem.OEMol(molecule) molecules.append(molecule_copy) input_molstream.close() if verbose: print(f"{len(molecules)} molecules read") end_time = time.time() elapsed_time = end_time - start_time if verbose: print(f"{elapsed_time:.3f} s elapsed") return molecules
def untar_full_alkethoh_and_freesolv_set(): """When running slow tests, we unpack the full AlkEthOH and FreeSolv test sets in advance to speed things up. See test_forcefield.py::test_alkethoh_parameters_assignment test_forcefield.py::test_freesolv_parameters_assignment """ import os import tarfile from openforcefield.utils import get_data_file_path molecule_dir_path = get_data_file_path('molecules') for tarfile_name in ['AlkEthOH_tripos.tar.gz', 'FreeSolv.tar.gz']: tarfile_path = os.path.join(molecule_dir_path, tarfile_name) with tarfile.open(tarfile_path, 'r:gz') as tar: tar.extractall(path=molecule_dir_path)
def test_get_mol2_charges(self): """Test OpenEyeToolkitWrapper for importing a mol2 file specifying partial charges""" toolkit_wrapper = OpenEyeToolkitWrapper() filename = get_data_file_path('molecules/toluene_charged.mol2') molecule = Molecule.from_file(filename, toolkit_registry=toolkit_wrapper) assert len(molecule._conformers) == 1 assert molecule._conformers[0].shape == (15, 3) target_charges = unit.Quantity( np.array([ -0.1342, -0.1271, -0.1271, -0.1310, -0.1310, -0.0765, -0.0541, 0.1314, 0.1286, 0.1286, 0.1303, 0.1303, 0.0440, 0.0440, 0.0440 ]), unit.elementary_charge) for pc1, pc2 in zip(molecule._partial_charges, target_charges): pc1_ul = pc1 / unit.elementary_charge pc2_ul = pc2 / unit.elementary_charge assert_almost_equal(pc1_ul, pc2_ul, decimal=4)
def test_merge_system(): """Test merging of a system created from AMBER and another created from SMIRNOFF.""" from .utils import create_system_from_amber, get_amber_file_path, get_alkethoh_file_path # Create System from AMBER prmtop_filename, inpcrd_filename = get_amber_file_path( 'cyclohexane_ethanol_0.4_0.6') system0, topology0, positions0 = create_system_from_amber( prmtop_filename, inpcrd_filename) # TODO: from openeye import oechem # Load simple OEMol alkethoh_mol2_filepath = get_alkethoh_file_path('AlkEthOH_c100')[0] ifs = oechem.oemolistream(alkethoh_mol2_filepath) mol = oechem.OEMol() flavor = oechem.OEIFlavor_Generic_Default | oechem.OEIFlavor_MOL2_Default | oechem.OEIFlavor_MOL2_Forcefield ifs.SetFlavor(oechem.OEFormat_MOL2, flavor) oechem.OEReadMolecule(ifs, mol) oechem.OETriposAtomNames(mol) # Load forcefield file. AlkEthOH_offxml_filename = utils.get_data_file_path( 'test_forcefields/Frosst_AlkEthOH.offxml') forcefield = ForceField(AlkEthOH_offxml_filename) # Create OpenMM System and Topology. off_mol = Molecule.from_openeye(mol, allow_undefined_stereo=True) off_top = Topology.from_molecules([off_mol]) system1 = forcefield.create_openmm_system(off_top) topology1 = structure.generateTopologyFromOEMol(mol) positions1 = structure.extractPositionsFromOEMol(mol) structure.merge_system(topology0, topology1, system0, system1, positions0, positions1, verbose=True)
def test_get_mol2_coordinates(self): """Test OpenEyeToolkitWrapper for importing a single set of molecule coordinates""" toolkit_wrapper = OpenEyeToolkitWrapper() filename = get_data_file_path('molecules/toluene.mol2') molecule1 = Molecule.from_file(filename, toolkit_registry=toolkit_wrapper) assert len(molecule1._conformers) == 1 assert molecule1._conformers[0].shape == (15, 3) assert_almost_equal(molecule1.conformers[0][5][1] / unit.angstrom, 22.98, decimal=2) # Test loading from file-like object with open(filename, 'r') as infile: molecule2 = Molecule(infile, file_format='MOL2', toolkit_registry=toolkit_wrapper) assert molecule1.is_isomorphic(molecule2) assert len(molecule2._conformers) == 1 assert molecule2._conformers[0].shape == (15, 3) assert_almost_equal(molecule2.conformers[0][5][1] / unit.angstrom, 22.98, decimal=2) # Test loading from gzipped mol2 import gzip with gzip.GzipFile(filename + '.gz', 'r') as infile: molecule3 = Molecule(infile, file_format='MOL2', toolkit_registry=toolkit_wrapper) assert molecule1.is_isomorphic(molecule3) assert len(molecule3._conformers) == 1 assert molecule3._conformers[0].shape == (15, 3) assert_almost_equal(molecule3.conformers[0][5][1] / unit.angstrom, 22.98, decimal=2)
mol_filename = 'ETH.mol2' # Define a few simulation parameters time_step = 2 * unit.femtoseconds # simulation timestep temperature = 300 * unit.kelvin # simulation temperature friction = 1 / unit.picosecond # collision rate num_steps = 1000000 # number of steps to run trj_freq = 1000 # number of steps per written trajectory frame data_freq = 1000 # number of steps per written simulation statistics # Load molecule and create pdb object pdb = PDBFile(pdb_filename) # Load a SMIRNOFF forcefield forcefield = ForceField( get_data_file_path('forcefield/Frosst_AlkEthOH_parmAtFrosst.offxml')) # Load molecule using OpenEye tools mol = oechem.OEGraphMol() ifs = oechem.oemolistream(mol_filename) # LPW: I don't understand the meaning of these lines. # flavor = oechem.OEIFlavor_Generic_Default | oechem.OEIFlavor_MOL2_Default | oechem.OEIFlavor_MOL2_Forcefield # ifs.SetFlavor( oechem.OEFormat_MOL2, flavor) oechem.OEReadMolecule(ifs, mol) oechem.OETriposAtomNames(mol) # Create the OpenMM system system = forcefield.createSystem(pdb.topology, [mol], nonbondedMethod=PME, nonbondedCutoff=1.0 * unit.nanometers, rigidWater=True)
def test_protein_structure(self): from simtk.openmm import app pdbfile = utils.get_data_file_path('proteins/T4-protein.pdb') proteinpdb = app.PDBFile(pdbfile) protein_structure = structure.generateProteinStructure(proteinpdb) assert isinstance(protein_structure, parmed.structure.Structure)
def test_component_combination(): """Test that a system still yields the same energy after rebuilding it out of its components """ from simtk import openmm from .utils import compare_system_energies, get_packmol_pdb_file_path # We've had issues where subsequent instances of a molecule might have zero charges # Here we'll try to catch this (and also explicitly check the charges) by re-building # a system out of its components # Create an OpenMM System from mol2 files containing a cyclohexane-ethanol mixture. AlkEthOH_offxml_filename = utils.get_data_file_path( 'test_forcefields/Frosst_AlkEthOH.offxml') forcefield = ForceField(AlkEthOH_offxml_filename) pdbfile = openmm.app.PDBFile( get_packmol_pdb_file_path('cyclohexane_ethanol_0.4_0.6')) sdf_file_paths = [ utils.get_data_file_path( os.path.join('systems', 'monomers', name + '.sdf')) for name in ('ethanol', 'cyclohexane') ] molecules = [Molecule.from_file(file_path) for file_path in sdf_file_paths] topology = Topology.from_openmm(pdbfile.topology, unique_molecules=molecules) system = forcefield.create_openmm_system(topology) # Convert System to a ParmEd Structure structure = parmed.openmm.topsystem.load_topology(topology.to_openmm(), system, pdbfile.positions) # Split the Structure into components, then re-compose it out of its components tmp = structure.split() strs, nums = [], [] for s, n in tmp: strs.append(s) nums.append(n) nums = [len(n) for n in nums] # Re-compose Structure from components new_structure = strs[0] * nums[0] for idx in range(1, len(nums)): new_structure += strs[idx] * nums[idx] # Swap in coordinates again new_structure.positions = structure.positions # Create System newsys = new_structure.createSystem(nonbondedMethod=openmm.app.NoCutoff, constraints=None, implicitSolvent=None) # Cross check energies groups0, groups1, energy0, energy1 = compare_system_energies( pdbfile.topology, pdbfile.topology, system, newsys, pdbfile.positions, verbose=False) # Also check that that the number of components is equal to the number I expect if not len(nums) == 2: print("Error: Test system has incorrect number of components.") raise Exception( 'Incorrect number of components in cyclohexane/ethanol test system.' ) # Also check that none of residues have zero charge for resnr in range(len(structure.residues)): abscharges = [ abs(structure.residues[resnr].atoms[idx].charge) for idx in range(len(structure.residues[resnr].atoms)) ] if sum(abscharges) == 0: raise Exception( 'Error: Residue %s in cyclohexane-ethanol test system has a charge of zero, which is incorrect.' % resnr)
#!/bin/env python """ Illustrate how to combine a SMIRNOFF parameterized small molecule with an AMBER parameterized protein using ParmEd. """ # # Load and parameterize the small molecule # # Load the small molecule from openforcefield.utils import get_data_file_path ligand_filename = get_data_file_path('molecules/toluene.mol2') molecule = Molecule.from_file(ligand_filename) # Load the smirnoff99Frosst force field from openforcefield.typing.engines import smirnoff forcefield = smirnoff.ForceField('test_forcefields/smirnoff99Frosst.offxml') # Create a ParmEd structure for the molecule molecule_structure = forcefield.create_parmed_structure( topology=molecule.to_topology(), positions=molecule.positions) print('Molecule:', molecule_structure) # # Load and parameterize the protein # # Load the protein topology
def test_tip3p_solvated_molecule_energy(): """Check the energy of a TIP3P solvated molecule is the same with SMIRNOFF and OpenMM. This test makes also use of defining the force field with multiple FFXML files, and test the energy of mixture systems (i.e. molecule plus water). """ # TODO remove this function when ParmEd#868 is fixed def add_water_bonds(system): """Hack to make tip3p waters work with ParmEd Structure.createSystem. Bond parameters need to be specified even when constrained. """ k_tip3p = 462750.4 * unit.kilojoule_per_mole / unit.nanometers**2 length_tip3p = 0.09572 * unit.nanometers for force in system.getForces(): if isinstance(force, openmm.HarmonicBondForce): force.addBond(particle1=0, particle2=1, length=length_tip3p, k=k_tip3p) force.addBond(particle1=0, particle2=2, length=length_tip3p, k=k_tip3p) monomers_dir = get_data_file_path(os.path.join('systems', 'monomers')) # Load TIP3P molecule tip3p_molecule = Molecule.from_file(tip3p_molecule_filename) tip3p_positions = tip3p_molecule.positions # Extract topology and positions top3p_topology = Topology.from_molecules(molecule) # Create OpenMM System tip3p_forcefield = ForceField(tip3p_offxml_filename) tip3p_openmm_system = tip3p_forcefield.create_system(tip3p_topology) # TODO remove this line when ParmEd#868 is fixed # Hack to make tip3p waters work with ParmEd Structure.createSystem. add_water_bonds(tip3p_openmm_system) tip3p_openmm_structure = parmed.openmm.topsystem.load_topology(topology.to_openmm(), tip3p_openmm_system, tip3p_positions) smirnoff_forcefield = ForceField(smirnoff99Frosst_offxml_filename) for monomer in ['ethanol', 'methane']: # Load molecule other_molecule_filepath = os.path.join(monomers_dir, monomer + '.mol2') other_molecule = Molecule.from_file(other_molecule_filepath) # Create ParmEd Structure other_molecule_structure = smirnoff_forcefield.create_structure(other_molecule.to_topology(), other_molecule.positions) # Merge molecule and OpenMM TIP3P water combined_structure = tip3p_openmm_structure + other_molecule_structure #structure.positions = np.append(tip3p_molecule.positions, molecule.positions, axis=0) # TODO: This doesn't seem necessary since positions are correctly concatenated already system = combined_structure.createSystem() energy_openmm = get_energy(system, combined_structure.positions) # Test the creation of system with multiple force field files. ff = ForceField(smirnoff_offxml_filename, tip3p_offxml_filename) combined_topology = Topology.from_molecules([tip3p_molecule, molecule]) system = ff.create_system(topology) # TODO remove this line when ParmEd#868 is fixed # Add bonds to match ParmEd hack above. add_water_bonds(system) energy_smirnoff = get_energy(system, structure.positions) # The energies of the systems must be the same. assert np.isclose(energy_openmm, energy_smirnoff), 'OpenMM: {}, SMIRNOFF: {}'.format( energy_openmm, energy_smirnoff)
def test_get_data_file_path(): """Test get_data_file_path()""" from openforcefield.utils import get_data_file_path filename = get_data_file_path('test_forcefields/tip3p.offxml') assert os.path.exists(filename)
pdb_filename = 'ETH-box.pdb' mol_filename = 'ETH.mol2' # Define a few simulation parameters time_step = 2*unit.femtoseconds # simulation timestep temperature = 300*unit.kelvin # simulation temperature friction = 1/unit.picosecond # collision rate num_steps = 1000000 # number of steps to run trj_freq = 1000 # number of steps per written trajectory frame data_freq = 1000 # number of steps per written simulation statistics # Load molecule and create pdb object pdb = PDBFile(pdb_filename) # Load a SMIRNOFF forcefield forcefield = ForceField(get_data_file_path('test_forcefields/Frosst_AlkEthOH_parmAtFrosst.offxml')) # Load molecule using OpenEye tools mol = oechem.OEGraphMol() ifs = oechem.oemolistream(mol_filename) # LPW: I don't understand the meaning of these lines. # flavor = oechem.OEIFlavor_Generic_Default | oechem.OEIFlavor_MOL2_Default | oechem.OEIFlavor_MOL2_Forcefield # ifs.SetFlavor( oechem.OEFormat_MOL2, flavor) oechem.OEReadMolecule(ifs, mol) oechem.OETriposAtomNames(mol) # Create the OpenMM system system = forcefield.createSystem(pdb.topology, [mol], nonbondedMethod=PME, nonbondedCutoff=1.0*unit.nanometers, rigidWater=True) # Set up an OpenMM simulation integrator = openmm.LangevinIntegrator(temperature, friction, time_step)
# from openforcefield.tests.utils import get_packmol_pdb_file_path, get_monomer_mol2_file_path, compare_system_energies from openforcefield.tests.utils import * from openforcefield.typing.engines.smirnoff import * # TODO: Fix these imports and unskip these tests import pytest pytestmark = pytest.mark.skip('tests in test_smirnoff are outdated and should be integrated with test_forcefield.py') #============================================================================================= # CONSTANTS #============================================================================================= # TODO: Move these to setup? # These paths should only be found in the test data directories, so we need to use get_data_file_path() AlkEthOH_offxml_filename = 'Frosst_AlkEthOH.offxml' AlkEthOH_molecules_filename = get_data_file_path('molecules/AlkEthOH_test_filt1_tripos.mol2') MiniDrugBank_molecules_filename = get_data_file_path('molecules/MiniDrugBank_tripos.mol2') #chargeincrement_offxml_filename = get_data_file_path('chargeincrement-test.offxml') # TODO: Swtich all paths to use os.path.join tip3p_molecule_filename = get_data_file_path(os.path.join('systems', 'monomers', 'tip3p_water.mol2')) # This is the production form of smirnoff99Frosst that should be found in the data directories smirnoff99Frosst_offxml_filename = 'smirnoff99Frosst.offxml' tip3p_offxml_filename = 'tip3p.offxml' # TODO: Add tests to compare RDKit and OpenEye derived forcefields to make sure they are the same # TODO: Move forcefields for testing only to a special test data directory, separate from the data/ paths that are automatically searched and populated with production force fields # # SMIRNOFF ForceField XML definitions for testing purposes #