def compare_single_mol_systems(mol, force_field): top = mol.to_topology() top.box_vectors = _box_vectors try: toolkit_sys = force_field.create_openmm_system( top, charge_from_molecules=[mol], ) except ( UnassignedBondParameterException, UnassignedAngleParameterException, UnassignedProperTorsionParameterException, ): pytest.xfail(f"Molecule failed! (missing valence parameters)\t{mol.to_inchi()}") toolkit_energy = _get_openmm_energies( toolkit_sys, box_vectors=_box_vectors, positions=mol.conformers[0] ) openff_sys = Interchange.from_smirnoff(force_field=force_field, topology=top) openff_sys.positions = mol.conformers[0] system_energy = get_openmm_energies(openff_sys, combine_nonbonded_forces=True) toolkit_energy.compare( system_energy, custom_tolerances={ "Bond": 1e-6 * kj_mol, "Angle": 1e-6 * kj_mol, "Torsion": 4e-5 * kj_mol, "Nonbonded": 1e-5 * kj_mol, }, )
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 = OFFBioTop.from_openmm(pdbfile.topology, unique_molecules=[mol]) top.mdtop = md.Topology.from_openmm(top.to_openmm()) box = pdbfile.topology.getPeriodicBoxVectors() box = box.value_in_unit(nm) * unit.nanometer out = Interchange.from_smirnoff(argon_ff, 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_from_openmm_single_mols(mol, n_mols): """ Test that ForceField.create_openmm_system and Interchange.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) * simtk_unit.angstrom top.box_vectors = np.eye(3) * np.asarray([15, 15, 15]) * simtk_unit.nanometer if n_mols == 1: positions = mol.conformers[0] elif n_mols == 2: positions = np.vstack( [mol.conformers[0], mol.conformers[0] + 3 * simtk_unit.nanometer] ) positions = positions * simtk_unit.angstrom toolkit_system = parsley.create_openmm_system(top) native_system = Interchange.from_smirnoff( force_field=parsley, topology=top ).to_openmm() toolkit_energy = _get_openmm_energies( omm_sys=toolkit_system, box_vectors=toolkit_system.getDefaultPeriodicBoxVectors(), positions=positions, ) native_energy = _get_openmm_energies( omm_sys=native_system, box_vectors=native_system.getDefaultPeriodicBoxVectors(), positions=positions, ) toolkit_energy.compare(native_energy)
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 = OFFBioTop.from_openmm( pdbfile.topology, unique_molecules=unique_molecules, ) top.mdtop = md.Topology.from_openmm(top.to_openmm()) box = pdbfile.topology.getPeriodicBoxVectors() box = box.value_in_unit(nm) * unit.nanometer out = Interchange.from_smirnoff(ff, 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, combine_nonbonded_forces=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_water_dimer(): tip3p = ForceField(get_test_file_path("tip3p.offxml")) water = Molecule.from_smiles("O") top = Topology.from_molecules(2 * [water]) top.mdtop = md.Topology.from_openmm(top.to_openmm()) pdbfile = openmm.app.PDBFile(get_test_file_path("water-dimer.pdb")) positions = pdbfile.positions openff_sys = Interchange.from_smirnoff(tip3p, 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) openff_sys["Electrostatics"].method = "cutoff" omm_energies_cutoff = get_gromacs_energies(openff_sys) lmp_energies = get_lammps_energies(openff_sys) lmp_energies.compare(omm_energies_cutoff)
def test_energies_single_mol(constrained, mol_smi): import mbuild as mb mol = Molecule.from_smiles(mol_smi) mol.generate_conformers(n_conformers=1) mol.name = "FOO" top = mol.to_topology() top.box_vectors = None # [10, 10, 10] * simtk_unit.nanometer if constrained: parsley = ForceField("openff-1.0.0.offxml") else: parsley = ForceField("openff_unconstrained-1.0.0.offxml") off_sys = Interchange.from_smirnoff(parsley, top) off_sys.handlers["Electrostatics"].method = "cutoff" 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=1, box=mb.Box(lengths=[10, 10, 10])) positions = packed_box.xyz * unit.nanometer off_sys.positions = positions # Compare directly to toolkit's reference implementation omm_energies = get_openmm_energies(off_sys, round_positions=8) 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, ) omm_energies.compare(reference_energies) mdp = "cutoff_hbonds" if constrained else "auto" # Compare GROMACS writer and OpenMM export gmx_energies = get_gromacs_energies(off_sys, mdp=mdp) custom_tolerances = { "Bond": 2e-5 * simtk_unit.kilojoule_per_mole, "Electrostatics": 2 * simtk_unit.kilojoule_per_mole, "vdW": 2 * simtk_unit.kilojoule_per_mole, "Nonbonded": 2 * simtk_unit.kilojoule_per_mole, "Angle": 1e-4 * simtk_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 = { "vdW": 5.0 * simtk_unit.kilojoule_per_mole, "Electrostatics": 5.0 * simtk_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 = OFFBioTop.from_openmm( omm_topology, unique_molecules=[ethanol, cyclohexane]) off_topology.mdtop = md.Topology.from_openmm(omm_topology) parsley = ForceField("openff_unconstrained-1.0.0.offxml") off_sys = Interchange.from_smirnoff(parsley, off_topology) off_sys.box = np.asarray( pdbfile.topology.getPeriodicBoxVectors().value_in_unit( simtk_unit.nanometer)) off_sys.positions = pdbfile.positions 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={ "Electrostatics": 2e-2 * simtk_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 * simtk_unit.kilojoule_per_mole, "Torsion": 1e-2 * simtk_unit.kilojoule_per_mole, "Electrostatics": 3200 * simtk_unit.kilojoule_per_mole, }, )
def compare_condensed_systems(mol, force_field): from openff.evaluator import unit as evaluator_unit from openff.evaluator.utils.packmol import pack_box mass_density = 500 * evaluator_unit.kilogram / evaluator_unit.meter ** 3 trj, assigned_residue_names = pack_box( molecules=[mol], number_of_copies=[100], mass_density=mass_density ) try: openff_top = Topology.from_openmm(trj.top.to_openmm(), unique_molecules=[mol]) except ValueError: print(f"Molecule failed! (conversion from OpenMM)\t{mol.to_inchi()}") return box_vectors = trj.unitcell_vectors[0] * simtk_unit.nanometer openff_top.box_vectors = box_vectors try: toolkit_sys = force_field.create_openmm_system( openff_top, charge_from_molecules=[mol], ) except ( UnassignedBondParameterException, UnassignedAngleParameterException, UnassignedProperTorsionParameterException, ): print(f"Molecule failed! (missing valence parameters)\t{mol.to_inchi()}") return positions = trj.xyz[0] * simtk_unit.nanometer toolkit_energy = _get_openmm_energies( toolkit_sys, box_vectors=box_vectors, positions=positions, ) openff_sys = Interchange.from_smirnoff(force_field=force_field, topology=openff_top) openff_sys.box = box_vectors openff_sys.positions = trj.xyz[0] * unit.nanometer new_sys = openff_sys.to_openmm(combine_nonbonded_forces=True) system_energy = _get_openmm_energies( new_sys, box_vectors=box_vectors, positions=positions, ) # Where energies to not precisely match, inspect all parameters in each force try: toolkit_energy.compare( system_energy, custom_tolerances={ "Bond": 1e-6 * kj_mol, "Angle": 1e-6 * kj_mol, "Torsion": 4e-5 * kj_mol, "Nonbonded": 1e-5 * kj_mol, }, ) except EnergyError as e: if "Torsion" in str(e): _compare_torsion_forces( _get_force(toolkit_sys, openmm.PeriodicTorsionForce), _get_force(new_sys, openmm.PeriodicTorsionForce), ) if "Nonbonded" in str(e): _compare_nonbonded_settings( _get_force(toolkit_sys, openmm.NonbondedForce), _get_force(new_sys, openmm.NonbondedForce), ) _compare_nonbonded_parameters( _get_force(toolkit_sys, openmm.NonbondedForce), _get_force(new_sys, openmm.NonbondedForce), ) if "Bond" in str(e): raise e if "Angle" in str(e): raise e