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_from_openmm_pdbfile(self, argon_ff, argon_top): pdb_file_path = get_test_file_path("10-argons.pdb") pdbfile = openmm.app.PDBFile(pdb_file_path) mol = Molecule.from_smiles("[#18]") top = Topology.from_openmm(pdbfile.topology, unique_molecules=[mol]) box = pdbfile.topology.getPeriodicBoxVectors() box = box.value_in_unit(nm) * unit.nanometer out = argon_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=argon_ff.create_openmm_system(top), box_vectors=pdbfile.topology.getPeriodicBoxVectors(), positions=pdbfile.getPositions(), hard_cutoff=True, ))
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_argon_buck(): mol = Molecule.from_smiles("[#18]") top = Topology.from_molecules([mol, mol]) A = 1.69e-8 * unit.Unit("erg / mol") * Avogadro B = 1 / (0.273 * unit.angstrom) C = 102e-12 * unit.Unit("erg / mol") * unit.angstrom**6 * Avogadro A = A.to(unit.Unit("kilojoule/mol")) B = B.to(unit.Unit("1 / nanometer")) C = C.to(unit.Unit("kilojoule / mol * nanometer ** 6")) r = 0.3 * unit.nanometer buck = BuckinghamvdWHandler() coul = ElectrostaticsMetaHandler() # Just to pass compatibility checks pot_key = PotentialKey(id="[#18]") pot = Potential(parameters={"A": A, "B": B, "C": C}) for top_atom in top.topology_atoms: top_key = TopologyKey(atom_indices=(top_atom.topology_atom_index, )) buck.slot_map.update({top_key: pot_key}) coul.charges.update({top_key: 0 * unit.elementary_charge}) buck.potentials[pot_key] = pot from openff.system.components.system import System out = System() out.handlers["Buckingham-6"] = buck out.handlers["Electrostatics"] = coul out.topology = top out.box = [10, 10, 10] * unit.nanometer out.positions = [[0, 0, 0], [0.3, 0, 0]] * unit.nanometer out.to_gro("out.gro", writer="internal") out.to_top("out.top", writer="internal") gmx_energies = get_gromacs_energies(out, mdp="cutoff_buck") omm_energies = get_openmm_energies(out) by_hand = A * exp(-B * r) - C * r**-6 gmx_energies.compare(omm_energies) resid = simtk_to_pint(gmx_energies.energies["Nonbonded"]) - by_hand assert resid < 1e-5 * unit.kilojoule / unit.mol
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_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_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 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 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