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_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_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 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_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 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 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, }, )