def test_from_toolkit_packmol_boxes(self, pdb_path, unique_molecules): """ Test loading some pre-prepared PACKMOL-generated systems. These use PDB files already prepared in the toolkit because PDB files are a pain. """ ff = ForceField("openff-1.0.0.offxml") pdb_file_path = get_data_file_path("systems/packmol_boxes/" + pdb_path) pdbfile = openmm.app.PDBFile(pdb_file_path) top = Topology.from_openmm( pdbfile.topology, unique_molecules=unique_molecules, ) box = pdbfile.topology.getPeriodicBoxVectors() box = box.value_in_unit(nm) * unit.nanometer out = ff.create_openff_system(top) out.box = box out.positions = pdbfile.getPositions() assert np.allclose( out.positions.to(unit.nanometer).magnitude, pdbfile.getPositions().value_in_unit(nm), ) get_openmm_energies(out, hard_cutoff=True).compare( _get_openmm_energies( omm_sys=ff.create_openmm_system(top), box_vectors=pdbfile.topology.getPeriodicBoxVectors(), positions=pdbfile.getPositions(), hard_cutoff=True, ))
def test_sanity_grompp(): """Basic test to ensure that a topology can be processed without errors""" mol = Molecule.from_smiles("CC") mol.generate_conformers(n_conformers=1) top = mol.to_topology() parsley = ForceField("openff-1.0.0.offxml") off_sys = parsley.create_openff_system(top) off_sys.box = [4, 4, 4] * np.eye(3) off_sys.positions = mol.conformers[0] / omm_unit.angstrom off_sys.to_gro("out.gro", writer="internal") off_sys.to_top("out.top", writer="internal") # TODO: Replace with intermol.gromacs.gmx_energy call after resolving # atomtype name differences that currently force -maxwarn 7 import os from pkg_resources import resource_filename mdp_file = resource_filename("intermol", "tests/gromacs/grompp.mdp") exit_code = os.system( f"gmx grompp -f {mdp_file} -c out.gro -p out.top -maxwarn 1") assert exit_code == 0
def test_basic_conversion_params(self, box): top = top_from_smiles("C") parsley = ForceField("openff_unconstrained-1.0.0.offxml") off_sys = parsley.create_openff_system(topology=top, box=box) # UnitArray(...) off_sys.positions = np.zeros(shape=(top.n_topology_atoms, 3)) struct = off_sys.to_parmed() sigma0 = struct.atoms[0].atom_type.sigma epsilon0 = struct.atoms[0].atom_type.epsilon sigma1 = struct.atoms[1].atom_type.sigma epsilon1 = struct.atoms[1].atom_type.epsilon bond_k = struct.bonds[0].type.k req = struct.bonds[0].type.req angle_k = struct.angles[0].type.k theteq = struct.angles[0].type.theteq assert sigma0 == pytest.approx(3.3996695084235347) assert epsilon0 == pytest.approx(0.1094) assert sigma1 == pytest.approx(2.649532787749369) assert epsilon1 == pytest.approx(0.0157) assert bond_k == pytest.approx(379.04658864565) assert req == pytest.approx(1.092888378383) assert angle_k == pytest.approx(37.143507635885) assert theteq == pytest.approx(107.5991506326) assert np.allclose(struct.box, np.array([40, 40, 40, 90, 90, 90]))
def test_from_openmm_single_mols(mol, n_mols): """ Test that ForceField.create_openmm_system and System.to_openmm produce objects with similar energies TODO: Tighten tolerances TODO: Test periodic and non-periodic """ parsley = ForceField(get_test_file_path("parsley.offxml")) mol = Molecule.from_smiles(mol) mol.generate_conformers(n_conformers=1) top = Topology.from_molecules(n_mols * [mol]) mol.conformers[0] -= np.min(mol.conformers) * unit.angstrom top.box_vectors = np.eye(3) * np.asarray([10, 10, 10]) * unit.nanometer if n_mols == 1: positions = mol.conformers[0] elif n_mols == 2: positions = np.vstack( [mol.conformers[0], mol.conformers[0] + 3 * unit.nanometer]) positions = positions * unit.angstrom toolkit_system = parsley.create_openmm_system(top) native_system = parsley.create_openff_system(topology=top).to_openmm() compare_system_energies( system1=toolkit_system, system2=native_system, positions=positions, box_vectors=top.box_vectors, )
def test_unsupported_handler(self): from openff.system.stubs import ForceField gbsa_ff = ForceField( get_data_file_path("test_forcefields/GBSA_HCT-1.0.offxml")) with pytest.raises(SMIRNOFFHandlersNotImplementedError, match=r"SMIRNOFF parameters not.*GBSA"): gbsa_ff.create_openff_system(topology=None)
def test_library_charge_assignment(self): from openff.toolkit.tests.test_forcefield import xml_ethanol_library_charges_ff from openff.system.stubs import ForceField forcefield = ForceField("openff-1.3.0.offxml") top = Topology.from_molecules( [Molecule.from_smiles(smi) for smi in ["[Na+]", "[Cl-]"]]) forcefield.create_openff_system(top)
def test_to_lammps_single_mols(mol, n_mols): """ Test that ForceField.create_openmm_system and System.to_openmm produce objects with similar energies TODO: Tighten tolerances TODO: Test periodic and non-periodic """ parsley = ForceField("openff_unconstrained-1.0.0.offxml") mol = Molecule.from_smiles(mol) mol.generate_conformers(n_conformers=1) top = Topology.from_molecules(n_mols * [mol]) mol.conformers[0] -= np.min(mol.conformers) * omm_unit.angstrom top.box_vectors = np.eye(3) * np.asarray([10, 10, 10]) * omm_unit.nanometer if n_mols == 1: positions = mol.conformers[0] elif n_mols == 2: positions = np.vstack( [mol.conformers[0], mol.conformers[0] + 3 * omm_unit.nanometer] ) positions = positions * omm_unit.angstrom openff_sys = parsley.create_openff_system(topology=top) openff_sys.positions = positions.value_in_unit(omm_unit.nanometer) openff_sys.box = top.box_vectors reference = get_openmm_energies( off_sys=openff_sys, round_positions=3, electrostatics=False ) lmp_energies = get_lammps_energies( off_sys=openff_sys, round_positions=3, electrostatics=False ) _write_lammps_input( off_sys=openff_sys, file_name="tmp.in", electrostatics=False, ) lmp_energies.compare( reference, custom_tolerances={ "Nonbonded": 999 * omm_unit.kilojoule_per_mole, "Torsion": 0.005 * omm_unit.kilojoule_per_mole, }, )
def test_internal_gromacs_writers(mol): mol = Molecule.from_smiles(mol) mol.name = "FOO" mol.generate_conformers(n_conformers=1) top = mol.to_topology() parsley = ForceField("openff_unconstrained-1.0.0.offxml") out = parsley.create_openff_system(top) out.box = [4, 4, 4] * np.eye(3) out.positions = mol.conformers[0] out.positions = np.round(out.positions, 2) openmm_sys = parsley.create_openmm_system(top) struct = pmd.openmm.load_topology( topology=top.to_openmm(), system=openmm_sys, xyz=out.positions.to(unit.angstrom), ) struct.box = [40, 40, 40, 90, 90, 90] with tempfile.TemporaryDirectory() as off_tempdir: with temporary_cd(off_tempdir): struct.save("reference.top") struct.save("reference.gro") out.to_top("internal.top", writer="internal") out.to_gro("internal.gro", writer="internal", decimal=3) compare_gro_files("internal.gro", "reference.gro") # TODO: Also compare to out.to_gro("parmed.gro", writer="parmed") reference_energy = run_gmx_energy( top_file="reference.top", gro_file="reference.gro", mdp_file=get_mdp_file("default"), ) internal_energy = run_gmx_energy( top_file="internal.top", gro_file="internal.gro", mdp_file=get_mdp_file("default"), ) reference_energy.compare( internal_energy, custom_tolerances={"Bond": 2e-2 * omm_unit.kilojoule_per_mole}, )
def test_nonbonded_compatibility(): mol = Molecule.from_smiles("CCO") mol.name = "FOO" mol.generate_conformers(n_conformers=1) top = mol.to_topology() positions = mol.conformers[0].in_units_of( omm_unit.nanometer) / omm_unit.nanometer box = [4, 4, 4] * np.eye(3) parsley = ForceField("openff_unconstrained-1.0.0.offxml") off_sys = parsley.create_openff_system(top) off_sys.box = box off_sys.handlers["Electrostatics"].method = "reaction-field" with pytest.raises(NonbondedCompatibilityError, match="reaction-field is not "): off_sys.to_openmm() off_sys.handlers["Electrostatics"].method = "Coulomb" with pytest.raises( MissingNonbondedCompatibilityError, match="Coulomb", ): off_sys.to_openmm() off_sys.box = None with pytest.raises( NonbondedCompatibilityError, match="Coulomb", ): off_sys.to_openmm() off_sys.box = box off_sys.handlers["Electrostatics"].method = "PME" with pytest.raises(MissingPositionsError): off_sys.to_gro("out.gro", writer="internal") off_sys.positions = positions # Ensure nothing obvious was mangled off_sys.to_top("out.top", writer="internal") off_sys.to_gro("out.gro", writer="internal")
def test_internal_gro_writer(): mol = Molecule.from_smiles("C") mol.generate_conformers(n_conformers=1) top = mol.to_topology() parsley = ForceField("openff-1.0.0.offxml") out = parsley.create_openff_system(top) out.box = [4, 4, 4] * np.eye(3) out.positions = mol.conformers[0] / omm_unit.nanometer out.to_gro("internal.gro", writer="internal") out.to_gro("parmed.gro", writer="parmed") with open("internal.gro", "r") as file1: with open("parmed.gro", "r") as file2: # Ignore first two lines and last line assert file1.readlines()[2:-1] == file2.readlines()[2:-1]
def test_parmed_openmm(tmpdir, smiles): tmpdir.chdir() parsley = ForceField("openff_unconstrained-1.0.0.offxml") mol = Molecule.from_smiles(smiles) mol.generate_conformers(n_conformers=1) top = Topology.from_molecules(mol) box = 4 * np.eye(3) * unit.nanometer with tempfile.TemporaryDirectory() as omm_tempdir: with temporary_cd(omm_tempdir): openff_openmm_pmd_gmx( topology=top, forcefield=parsley, box=box, prefix="via_openmm", ) ener1, ener1_file = gmx_energy( top="via_openmm.top", gro="via_openmm.gro", mdp=resource_filename("intermol", "tests/gromacs/grompp.mdp"), ) with tempfile.TemporaryDirectory() as off_tempdir: with temporary_cd(off_tempdir): openff_pmd_gmx_indirect( topology=top, forcefield=parsley, box=box, prefix="via_conversion", ) ener2, ener2_file = gmx_energy( top="via_conversion.top", gro="via_conversion.gro", mdp=resource_filename("intermol", "tests/gromacs/grompp.mdp"), ) compare_energies(ener1, ener2) with tempfile.TemporaryDirectory() as off_tempdir: with temporary_cd(off_tempdir): openff_pmd_gmx_direct( topology=top, forcefield=parsley, box=box, prefix="via_call", ) ener3, ener3_file = gmx_energy( top="via_call.top", gro="via_call.gro", mdp=resource_filename("intermol", "tests/gromacs/grompp.mdp"), ) compare_energies(ener2, ener3)
def test_argon(n_mol): from openff.system.utils import get_test_file_path ar_ff = ForceField(get_test_file_path("argon.offxml")) mol = Molecule.from_smiles("[#18]") mol.add_conformer(np.array([[0, 0, 0]]) * omm_unit.angstrom) mol.name = "FOO" top = Topology.from_molecules(n_mol * [mol]) off_sys = ar_ff.create_openff_system(top) mol.to_file("out.xyz", file_format="xyz") compound: mb.Compound = mb.load("out.xyz") packed_box: mb.Compound = mb.fill_box( compound=compound, n_compounds=[n_mol], box=mb.Box([4, 4, 4]), ) positions = packed_box.xyz * unit.nanometer positions = np.round(positions, 3) off_sys.positions = positions box = np.asarray(packed_box.box.lengths) * unit.nanometer off_sys.box = box omm_energies = get_openmm_energies( off_sys, round_positions=8, hard_cutoff=True, electrostatics=False ) gmx_energies = get_gromacs_energies( off_sys, writer="internal", electrostatics=False ) lmp_energies = get_lammps_energies(off_sys) omm_energies.compare(lmp_energies) omm_energies.compare( gmx_energies, custom_tolerances={ "Nonbonded": 2e-5 * omm_unit.kilojoule_per_mole, }, )
def test_getitem(): """Test behavior of System.__getitem__""" mol = Molecule.from_smiles("CCO") parsley = ForceField("openff-1.0.0.offxml") out = parsley.create_openff_system(mol.to_topology()) out.box = [4, 4, 4] assert not out.positions np.testing.assert_equal(out["box"].m, (4 * np.eye(3) * unit.nanometer).m) np.testing.assert_equal(out["box"].m, out["box_vectors"].m) assert out["Bonds"] == out.handlers["Bonds"] with pytest.raises(LookupError, match="Only str"): out[1] with pytest.raises(LookupError, match="Could not find"): out["CMAPs"]
def test_water_dimer(): from openff.system.utils import get_test_file_path tip3p = ForceField(get_test_file_path("tip3p.offxml")) water = Molecule.from_smiles("O") top = Topology.from_molecules(2 * [water]) pdbfile = openmm.app.PDBFile(get_test_file_path("water-dimer.pdb")) positions = np.array(pdbfile.positions / omm_unit.nanometer) * unit.nanometer openff_sys = tip3p.create_openff_system(top) openff_sys.positions = positions openff_sys.box = [10, 10, 10] * unit.nanometer omm_energies = get_openmm_energies( openff_sys, hard_cutoff=True, electrostatics=False, ) toolkit_energies = _get_openmm_energies( tip3p.create_openmm_system(top), openff_sys.box, openff_sys.positions, hard_cutoff=True, electrostatics=False, ) omm_energies.compare(toolkit_energies) # TODO: Fix GROMACS energies by handling SETTLE constraints # gmx_energies, _ = get_gromacs_energies(openff_sys) # compare_gromacs_openmm(omm_energies=omm_energies, gmx_energies=gmx_energies) lmp_energies = get_lammps_energies(openff_sys, electrostatics=False) lmp_energies.compare(omm_energies)
def test_bond_potential_handler(self): top = Topology.from_molecules(Molecule.from_smiles("O=O")) bond_handler = BondHandler(version=0.3) bond_parameter = BondHandler.BondType( smirks="[*:1]~[*:2]", k=1.5 * omm_unit.kilocalorie_per_mole / omm_unit.angstrom**2, length=1.5 * omm_unit.angstrom, id="b1000", ) bond_handler.add_parameter(bond_parameter.to_dict()) from openff.system.stubs import ForceField forcefield = ForceField() forcefield.register_parameter_handler(bond_handler) bond_potentials = forcefield["Bonds"].create_potential(top) pot = bond_potentials.potentials[bond_potentials.slot_map["(0, 1)"]] kcal_mol_a2 = unit.Unit("kilocalorie / (angstrom ** 2 * mole)") assert pot.parameters["k"].to(kcal_mol_a2).magnitude == pytest.approx( 1.5)
def test_water_dimer(): """Test that a water dimer can be written and the files can be grommp'd""" from openff.system.utils import get_test_file_path tip3p = ForceField(get_test_file_path("tip3p.offxml")) water = Molecule.from_smiles("O") top = Topology.from_molecules(2 * [water]) from simtk import openmm from simtk import unit as omm_unit pdbfile = openmm.app.PDBFile(get_test_file_path("water-dimer.pdb")) positions = np.array( pdbfile.positions / omm_unit.nanometer) * unit.nanometer openff_sys = tip3p.create_openff_system(top) openff_sys.positions = positions openff_sys.box = [10, 10, 10] * unit.nanometer gmx_energies = get_gromacs_energies(openff_sys, writer="internal") assert gmx_energies is not None
def test_angle_potential_handler(self): top = Topology.from_molecules(Molecule.from_smiles("CCC")) angle_handler = AngleHandler(version=0.3) angle_parameter = AngleHandler.AngleType( smirks="[*:1]~[*:2]~[*:3]", k=2.5 * omm_unit.kilocalorie_per_mole / omm_unit.radian**2, angle=100 * omm_unit.degree, id="b1000", ) angle_handler.add_parameter(angle_parameter.to_dict()) from openff.system.stubs import ForceField forcefield = ForceField() forcefield.register_parameter_handler(angle_handler) angle_potentials = forcefield["Angles"].create_potential(top) top_key = TopologyKey(atom_indices=(0, 1, 2)) pot = angle_potentials.potentials[angle_potentials.slot_map[top_key]] kcal_mol_rad2 = unit.Unit("kilocalorie / (mole * radian ** 2)") assert pot.parameters["k"].to( kcal_mol_rad2).magnitude == pytest.approx(2.5)
def openff_pmd_gmx_direct( topology: Topology, forcefield: ForceField, box: ArrayQuantity, prefix: str, ) -> None: off_sys = forcefield.create_openff_system(topology=topology) off_sys.box = box ref_mol = topology.topology_molecules[0].reference_molecule off_top_positions = ref_mol.conformers[0] / omm_unit.nanometer * unit.nanometer # TODO: Update this when better processing of OFFTop positions is supported off_sys.positions = off_top_positions off_sys.to_gro(prefix + ".gro") off_sys.to_top(prefix + ".top")
def test_library_charge_assignment(self): from openff.system.stubs import ForceField forcefield = ForceField("openff-1.3.0.offxml") forcefield.deregister_parameter_handler("ToolkitAM1BCC") top = Topology.from_molecules( [Molecule.from_smiles(smi) for smi in ["[Na+]", "[Cl-]"]]) reference = forcefield.create_openmm_system(top) new = forcefield.create_openff_system(top) compare_charges_omm_off(reference, new)
def openff_openmm_pmd_gmx( topology: Topology, forcefield: ForceField, box: ArrayQuantity, prefix: str, ) -> None: """Pipeline to write GROMACS files from and OpenMM system through ParmEd""" topology.box_vectors = box.to(unit.nanometer).magnitude * omm_unit.nanometer omm_sys = forcefield.create_openmm_system(topology) struct = pmd.openmm.load_topology( system=omm_sys, topology=topology.to_openmm(), xyz=topology.topology_molecules[0].reference_molecule.conformers[0], ) # Assign dummy residue names, GROMACS will not accept empty strings # TODO: Patch upstream? for res in struct.residues: res.name = "FOO" struct.save(prefix + ".gro") struct.save(prefix + ".top")
def parsley(self): return ForceField("openff-1.0.0.offxml")
def test_energies_single_mol(constrained, n_mol, mol_smi): mol = Molecule.from_smiles(mol_smi) mol.generate_conformers(n_conformers=1) mol.name = "FOO" top = Topology.from_molecules(n_mol * [mol]) if constrained: parsley = ForceField("openff-1.0.0.offxml") else: parsley = ForceField("openff_unconstrained-1.0.0.offxml") off_sys = parsley.create_openff_system(top) mol.to_file("out.xyz", file_format="xyz") compound: mb.Compound = mb.load("out.xyz") packed_box: mb.Compound = mb.fill_box( compound=compound, n_compounds=[n_mol], density=500, # kg/m^3 ) positions = packed_box.xyz * unit.nanometer off_sys.positions = positions box = np.asarray(packed_box.box.lengths) * unit.nanometer if np.any(box < 4 * unit.nanometer): off_sys.box = np.array([4, 4, 4]) * unit.nanometer else: off_sys.box = box # Compare directly to toolkit's reference implementation omm_energies = get_openmm_energies( off_sys, round_positions=8, hard_cutoff=True, electrostatics=False ) omm_reference = parsley.create_openmm_system(top) reference_energies = _get_openmm_energies( omm_sys=omm_reference, box_vectors=off_sys.box, positions=off_sys.positions, round_positions=8, hard_cutoff=True, electrostatics=False, ) try: omm_energies.compare(reference_energies) except EnergyError as e: if "Nonbonded" in str(e): # If nonbonded energies differ, at least ensure that the nonbonded # parameters on each particle match from openff.system.tests.utils import ( _get_charges_from_openmm_system, _get_lj_params_from_openmm_system, ) else: raise e omm_sys = off_sys.to_openmm() np.testing.assert_equal( np.asarray([*_get_charges_from_openmm_system(omm_sys)]), np.asarray([*_get_charges_from_openmm_system(omm_reference)]), ) np.testing.assert_equal( np.asarray([*_get_lj_params_from_openmm_system(omm_sys)]), np.asarray([*_get_lj_params_from_openmm_system(omm_reference)]), ) mdp = "cutoff_hbonds" if constrained else "cutoff" # Compare GROMACS writer and OpenMM export gmx_energies = get_gromacs_energies(off_sys, mdp=mdp, electrostatics=False) custom_tolerances = { "Bond": 2e-5 * n_mol * omm_unit.kilojoule_per_mole, "Nonbonded": 1e-3 * n_mol * omm_unit.kilojoule_per_mole, } if constrained: # GROMACS might use the initial bond lengths, not the equilibrium bond lengths, # in the initial configuration, making angles differ slightly custom_tolerances.update( { "Angle": 5e-2 * n_mol * omm_unit.kilojoule_per_mole, "Nonbonded": 2.0 * n_mol * omm_unit.kilojoule_per_mole, } ) gmx_energies.compare( omm_energies, custom_tolerances=custom_tolerances, ) if not constrained: other_energies = get_openmm_energies( off_sys, round_positions=8, hard_cutoff=True, electrostatics=True, ) lmp_energies = get_lammps_energies(off_sys) custom_tolerances = { "Nonbonded": 0.5 * n_mol * omm_unit.kilojoule_per_mole, } lmp_energies.compare(other_energies, custom_tolerances=custom_tolerances)
def parsley_unconstrained(self): return ForceField("openff_unconstrained-1.0.0.offxml")
def test_packmol_boxes(toolkit_file_path): # TODO: Isolate a set of systems here instead of using toolkit data # TODO: Fix nonbonded energy differences from openff.toolkit.utils import get_data_file_path pdb_file_path = get_data_file_path(toolkit_file_path) pdbfile = openmm.app.PDBFile(pdb_file_path) ethanol = Molecule.from_smiles("CCO") cyclohexane = Molecule.from_smiles("C1CCCCC1") omm_topology = pdbfile.topology off_topology = Topology.from_openmm( omm_topology, unique_molecules=[ethanol, cyclohexane] ) parsley = ForceField("openff_unconstrained-1.0.0.offxml") off_sys = parsley.create_openff_system(off_topology) off_sys.box = np.asarray( pdbfile.topology.getPeriodicBoxVectors() / omm_unit.nanometer, ) off_sys.positions = np.asarray( pdbfile.positions / omm_unit.nanometer, ) sys_from_toolkit = parsley.create_openmm_system(off_topology) omm_energies = get_openmm_energies(off_sys, hard_cutoff=True, electrostatics=False) reference = _get_openmm_energies( sys_from_toolkit, off_sys.box, off_sys.positions, hard_cutoff=True, electrostatics=False, ) omm_energies.compare( reference, custom_tolerances={ "Nonbonded": 2e-2 * omm_unit.kilojoule_per_mole, }, ) # custom_tolerances={"HarmonicBondForce": 1.0} # Compare GROMACS writer and OpenMM export gmx_energies = get_gromacs_energies(off_sys, electrostatics=False) omm_energies_rounded = get_openmm_energies( off_sys, round_positions=8, hard_cutoff=True, electrostatics=False, ) omm_energies_rounded.compare( other=gmx_energies, custom_tolerances={ "Angle": 1e-2 * omm_unit.kilojoule_per_mole, "Torsion": 1e-2 * omm_unit.kilojoule_per_mole, "Nonbonded": 3200 * omm_unit.kilojoule_per_mole, }, )
def ammonia_ff(self): """Fixture that loads an SMIRNOFF XML for ammonia""" return ForceField(get_test_file_path("ammonia.offxml"))
def argon_ff(self): """Fixture that loads an SMIRNOFF XML for argon""" return ForceField(get_test_file_path("argon.offxml"))
def test_ethanol_opls(): mol = Molecule.from_smiles("CC") mol.generate_conformers(n_conformers=1) top = mol.to_topology() parsley = ForceField("openff-1.0.0.offxml") out = parsley.create_openff_system(top) out.box = [4, 4, 4] out.positions = mol.conformers[0] out.positions = np.round(out.positions, 2) rb_torsions = RBTorsionHandler() smirks = "[#1:1]-[#6X4:2]-[#6X4:3]-[#1:4]" pot_key = PotentialKey(id=smirks) for proper in top.propers: top_key = TopologyKey(atom_indices=tuple(a.topology_atom_index for a in proper)) rb_torsions.slot_map.update({top_key: pot_key}) # Values from HC-CT-CT-HC RB torsion # https://github.com/mosdef-hub/foyer/blob/7816bf53a127502520a18d76c81510f96adfdbed/foyer/forcefields/xml/oplsaa.xml#L2585 pot = Potential( parameters={ "c0": 0.6276 * kj_mol, "c1": 1.8828 * kj_mol, "c2": 0.0 * kj_mol, "c3": -2.5104 * kj_mol, "c4": 0.0 * kj_mol, "c5": 0.0 * kj_mol, }) rb_torsions.potentials.update({pot_key: pot}) out.handlers.update({"RBTorsions": rb_torsions}) out.handlers.pop("ProperTorsions") gmx = get_openmm_energies(out, round_positions=3).energies["Torsion"] omm = get_gromacs_energies(out).energies["Torsion"] assert (gmx - omm).value_in_unit(omm_unit.kilojoule_per_mole) < 1e-3 # Given that these force constants are copied from Foyer's OPLS-AA file, # compare to processing through the current MoSDeF pipeline try: import foyer import mbuild except ModuleNotFoundError: return comp = mbuild.load("CC", smiles=True) comp.xyz = mol.conformers[0].value_in_unit(omm_unit.nanometer) ff = foyer.Forcefield(name="oplsaa") from_foyer = ff.apply(comp) from_foyer.box = [40, 40, 40, 90, 90, 90] from_foyer.save("from_foyer.top") from_foyer.save("from_foyer.gro") rb_torsion_energy_from_foyer = run_gmx_energy( top_file="from_foyer.top", gro_file="from_foyer.gro", mdp_file=get_mdp_file("default"), ).energies["Torsion"] assert (omm - rb_torsion_energy_from_foyer).value_in_unit( omm_unit.kilojoule_per_mole) < 1e-3