def test_atom_ordering(self):
        """Test that atom indices in bonds are ordered consistently between the slot map and topology"""
        import foyer

        from openff.interchange.components.interchange import Interchange
        from openff.interchange.drivers import (
            get_gromacs_energies,
            get_lammps_energies,
            get_openmm_energies,
        )

        oplsaa = foyer.forcefields.load_OPLSAA()

        benzene = Molecule.from_file(get_test_file_path("benzene.sdf"))
        benzene.name = "BENZ"
        biotop = OFFBioTop.from_molecules(benzene)
        biotop.mdtop = md.Topology.from_openmm(biotop.to_openmm())
        out = Interchange.from_foyer(force_field=oplsaa, topology=biotop)
        out.box = [4, 4, 4]
        out.positions = benzene.conformers[0]

        # Violates OPLS-AA, but the point here is just to make sure everything runs
        out["vdW"].mixing_rule = "lorentz-berthelot"

        get_gromacs_energies(out)
        get_openmm_energies(out)
        get_lammps_energies(out)
示例#2
0
    def test_electrostatics_charge_increments(self):
        top = OFFBioTop.from_molecules(
            Molecule.from_mapped_smiles("[Cl:1][H:2]"))

        charge_increment_handler = ChargeIncrementModelHandler(version=0.3)
        charge_increment_handler.add_parameter({
            "smirks":
            "[#17:1]-[#1:2]",
            "charge_increment1":
            0.1 * simtk_unit.elementary_charge,
            "charge_increment2":
            -0.1 * simtk_unit.elementary_charge,
        })

        parameter_handlers = [
            ElectrostaticsHandler(version=0.3),
            charge_increment_handler,
        ]

        electrostatics_handler = SMIRNOFFElectrostaticsHandler._from_toolkit(
            parameter_handlers, top)

        # AM1-Mulliken charges are [-0.168,  0.168], increments are [0.1, -0.1],
        # sum is [-0.068,  0.068]
        np.testing.assert_allclose(
            [
                charge.m_as(unit.e)
                for charge in electrostatics_handler.charges.values()
            ],
            [-0.068, 0.068],
        )
        def interchanges_from_path(molecule_path):
            molecule_or_molecules = Molecule.from_file(molecule_path)
            mol_name = Path(molecule_path).stem[0:4].upper()

            if isinstance(molecule_or_molecules, list):
                for idx, mol in enumerate(molecule_or_molecules):
                    mol.name = f"{mol_name}_{idx}"
            else:
                molecule_or_molecules.name = mol_name

            off_bio_top = OFFBioTop.from_molecules(molecule_or_molecules)
            off_bio_top.mdtop = md.Topology.from_openmm(off_bio_top.to_openmm())
            openff_interchange = Interchange.from_foyer(off_bio_top, oplsaa)

            if isinstance(molecule_or_molecules, list):
                openff_interchange.positions = np.vstack(
                    tuple(
                        molecule.conformers[0].value_in_unit(omm_unit.nanometer)
                        for molecule in molecule_or_molecules
                    )
                )
            else:
                openff_interchange.positions = molecule_or_molecules.conformers[
                    0
                ].value_in_unit(omm_unit.nanometer)

            openff_interchange.box = [4, 4, 4]

            parmed_struct = pmd.openmm.load_topology(off_bio_top.to_openmm())
            parmed_struct.positions = openff_interchange.positions.m_as(unit.angstrom)
            parmed_struct.box = [40, 40, 40, 90, 90, 90]

            return openff_interchange, parmed_struct
示例#4
0
    def test_bond_potential_handler(self):
        top = OFFBioTop.from_molecules(Molecule.from_smiles("O=O"))

        bond_handler = BondHandler(version=0.3)
        bond_parameter = BondHandler.BondType(
            smirks="[*:1]~[*:2]",
            k=1.5 * simtk_unit.kilocalorie_per_mole / simtk_unit.angstrom**2,
            length=1.5 * simtk_unit.angstrom,
            id="b1000",
        )
        bond_handler.add_parameter(bond_parameter.to_dict())

        from openff.toolkit.typing.engines.smirnoff import ForceField

        forcefield = ForceField()
        forcefield.register_parameter_handler(bond_handler)
        bond_potentials = SMIRNOFFBondHandler._from_toolkit(
            parameter_handler=forcefield["Bonds"],
            topology=top,
        )

        top_key = TopologyKey(atom_indices=(0, 1))
        pot_key = bond_potentials.slot_map[top_key]
        assert pot_key.associated_handler == "Bonds"
        pot = bond_potentials.potentials[pot_key]

        kcal_mol_a2 = unit.Unit("kilocalorie / (angstrom ** 2 * mole)")
        assert pot.parameters["k"].to(kcal_mol_a2).magnitude == pytest.approx(
            1.5)
示例#5
0
def test_residues():
    pdb = app.PDBFile(get_test_file_path("ALA_GLY/ALA_GLY.pdb"))
    traj = md.load(get_test_file_path("ALA_GLY/ALA_GLY.pdb"))
    mol = Molecule(get_test_file_path("ALA_GLY/ALA_GLY.sdf"),
                   file_format="sdf")

    top = OFFBioTop.from_openmm(pdb.topology, unique_molecules=[mol])
    top.mdtop = traj.top

    assert top.n_topology_atoms == 29
    assert top.mdtop.n_residues == 4
    assert [r.name for r in top.mdtop.residues] == ["ACE", "ALA", "GLY", "NME"]

    ff = ForceField("openff-1.3.0.offxml")
    off_sys = Interchange.from_smirnoff(ff, top)

    # Assign positions and box vectors in order to run MM
    off_sys.positions = pdb.positions
    off_sys.box = [4.8, 4.8, 4.8]

    # Just ensure that a single-point energy can be obtained without error
    get_openmm_energies(off_sys)

    assert len(top.mdtop.select("resname ALA")) == 10
    assert [*off_sys.topology.mdtop.residues][-1].n_atoms == 6
示例#6
0
    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,
            )
        )
示例#7
0
def top_from_smiles(
    smiles: str,
    n_molecules: int = 1,
) -> OFFBioTop:
    """Create a gas phase OpenFF Topology from a single-molecule SMILES

    Parameters
    ----------
    smiles : str
        The SMILES of the input molecule
    n_molecules : int, optional, default = 1
        The number of copies of the SMILES molecule from which to
        compose a topology

    Returns
    -------
    top : opennff.interchange.components.mdtraj.OFFBioTop
        A single-molecule, gas phase-like topology

    """
    mol = Molecule.from_smiles(smiles)
    mol.generate_conformers(n_conformers=1)
    top = OFFBioTop.from_molecules(n_molecules * [mol])
    top.mdtop = md.Topology.from_openmm(
        top.to_openmm())  # type: ignore[attr-defined]
    # Add dummy box vectors
    # TODO: Revisit if/after Topology.is_periodic
    top.box_vectors = np.eye(3) * 10 * simtk_unit.nanometer
    return top
示例#8
0
    def test_electrostatics_library_charges(self):
        top = OFFBioTop.from_molecules(Molecule.from_smiles("C"))

        library_charge_handler = LibraryChargeHandler(version=0.3)
        library_charge_handler.add_parameter({
            "smirks":
            "[#6X4:1]-[#1:2]",
            "charge1":
            -0.1 * simtk_unit.elementary_charge,
            "charge2":
            0.025 * simtk_unit.elementary_charge,
        })

        parameter_handlers = [
            ElectrostaticsHandler(version=0.3),
            library_charge_handler,
        ]

        electrostatics_handler = SMIRNOFFElectrostaticsHandler._from_toolkit(
            parameter_handlers, top)

        np.testing.assert_allclose(
            [
                charge.m_as(unit.e)
                for charge in electrostatics_handler.charges.values()
            ],
            [-0.1, 0.025, 0.025, 0.025, 0.025],
        )
示例#9
0
    def test_angle_potential_handler(self):
        top = OFFBioTop.from_molecules(Molecule.from_smiles("CCC"))

        angle_handler = AngleHandler(version=0.3)
        angle_parameter = AngleHandler.AngleType(
            smirks="[*:1]~[*:2]~[*:3]",
            k=2.5 * simtk_unit.kilocalorie_per_mole / simtk_unit.radian**2,
            angle=100 * simtk_unit.degree,
            id="b1000",
        )
        angle_handler.add_parameter(angle_parameter.to_dict())

        forcefield = ForceField()
        forcefield.register_parameter_handler(angle_handler)
        angle_potentials = SMIRNOFFAngleHandler._from_toolkit(
            parameter_handler=forcefield["Angles"],
            topology=top,
        )

        top_key = TopologyKey(atom_indices=(0, 1, 2))
        pot_key = angle_potentials.slot_map[top_key]
        assert pot_key.associated_handler == "Angles"
        pot = angle_potentials.potentials[pot_key]

        kcal_mol_rad2 = unit.Unit("kilocalorie / (mole * radian ** 2)")
        assert pot.parameters["k"].to(
            kcal_mol_rad2).magnitude == pytest.approx(2.5)
示例#10
0
def from_openmm(topology=None, system=None, positions=None, box_vectors=None):
    from openff.interchange.components.interchange import Interchange

    openff_sys = Interchange()

    if system:
        for force in system.getForces():
            if isinstance(force, openmm.NonbondedForce):
                vdw, coul = _convert_nonbonded_force(force)
                openff_sys.add_handler(handler_name="vdW", handler=vdw)
                openff_sys.add_handler(handler_name="Electrostatics",
                                       handler=coul)
            if isinstance(force, openmm.HarmonicBondForce):
                bond_handler = _convert_harmonic_bond_force(force)
                openff_sys.add_handler(handler_name="Bonds",
                                       handler=bond_handler)
            if isinstance(force, openmm.HarmonicAngleForce):
                angle_handler = _convert_harmonic_angle_force(force)
                openff_sys.add_handler(handler_name="Angles",
                                       handler=angle_handler)
            if isinstance(force, openmm.PeriodicTorsionForce):
                proper_torsion_handler = _convert_periodic_torsion_force(force)
                openff_sys.add_handler(handler_name="PeriodicTorsions",
                                       handler=proper_torsion_handler)

    if topology:
        import mdtraj as md

        from openff.interchange.components.mdtraj import OFFBioTop

        mdtop = md.Topology.from_openmm(topology)
        top = OFFBioTop()
        top.mdtop = mdtop

        openff_sys.topoology = top

    if positions:
        openff_sys.positions = positions

    if box_vectors:
        openff_sys.box = box_vectors

    return openff_sys
    def test_argon_buck(self):
        """Test that Buckingham potentials are supported and can be exported"""
        from openff.interchange.components.smirnoff import SMIRNOFFElectrostaticsHandler

        mol = Molecule.from_smiles("[#18]")
        top = OFFBioTop.from_molecules([mol, mol])
        top.mdtop = md.Topology.from_openmm(top.to_openmm())

        # http://www.sklogwiki.org/SklogWiki/index.php/Argon#Buckingham_potential
        erg_mol = unit.erg / unit.mol * float(unit.avogadro_number)
        A = 1.69e-8 * erg_mol
        B = 1 / (0.273 * unit.angstrom)
        C = 102e-12 * erg_mol * unit.angstrom**6

        r = 0.3 * unit.nanometer

        buck = BuckinghamvdWHandler()
        coul = SMIRNOFFElectrostaticsHandler(method="pme")

        pot_key = PotentialKey(id="[#18]")
        pot = Potential(parameters={"A": A, "B": B, "C": C})

        for atom in top.mdtop.atoms:
            top_key = TopologyKey(atom_indices=(atom.index, ))
            buck.slot_map.update({top_key: pot_key})

            coul.slot_map.update({top_key: pot_key})
            coul.potentials.update({
                pot_key:
                Potential(parameters={"charge": 0 * unit.elementary_charge})
            })

        buck.potentials[pot_key] = pot

        out = Interchange()
        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")

        omm_energies = get_openmm_energies(out)
        by_hand = A * exp(-B * r) - C * r**-6

        resid = omm_energies.energies["Nonbonded"] - by_hand
        assert resid < 1e-5 * unit.kilojoule / unit.mol

        # TODO: Add back comparison to GROMACS energies once GROMACS 2020+
        # supports Buckingham potentials
        with pytest.raises(GMXMdrunError):
            get_gromacs_energies(out, mdp="cutoff_buck")
示例#12
0
def test_to_lammps_single_mols(mol, n_mols):
    """
    Test that Interchange.to_openmm Interchange.to_lammps report sufficiently 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 = OFFBioTop.from_molecules(n_mols * [mol])
    top.mdtop = md.Topology.from_openmm(top.to_openmm())
    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 = Interchange.from_smirnoff(parsley, 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,
    )

    lmp_energies = get_lammps_energies(
        off_sys=openff_sys,
        round_positions=3,
    )

    _write_lammps_input(
        off_sys=openff_sys,
        file_name="tmp.in",
    )

    lmp_energies.compare(
        reference,
        custom_tolerances={
            "Nonbonded": 100 * omm_unit.kilojoule_per_mole,
            "Electrostatics": 100 * omm_unit.kilojoule_per_mole,
            "vdW": 100 * omm_unit.kilojoule_per_mole,
            "Torsion": 3e-5 * omm_unit.kilojoule_per_mole,
        },
    )
示例#13
0
def test_unsupported_mixing_rule():
    molecules = [create_ethanol()]
    pdbfile = app.PDBFile(get_data_file_path("systems/test_systems/1_ethanol.pdb"))
    topology = OFFBioTop.from_openmm(pdbfile.topology, unique_molecules=molecules)
    topology.mdtop = md.Topology.from_openmm(topology.to_openmm())

    forcefield = ForceField("test_forcefields/test_forcefield.offxml")
    openff_sys = Interchange.from_smirnoff(force_field=forcefield, topology=topology)

    openff_sys["vdW"].mixing_rule = "geometric"

    with pytest.raises(UnsupportedExportError, match="default NonbondedForce"):
        openff_sys.to_openmm(combine_nonbonded_forces=True)
示例#14
0
    def oplsaa_interchange_ethanol(self, oplsaa):

        molecule = Molecule.from_file(
            get_test_files_dir_path("foyer_test_molecules") + "/ethanol.sdf"
        )
        molecule.name = "ETH"

        top = OFFBioTop.from_molecules(molecule)
        top.mdtop = md.Topology.from_openmm(top.to_openmm())
        oplsaa = foyer.Forcefield(name="oplsaa")
        interchange = Interchange.from_foyer(topology=top, force_field=oplsaa)
        interchange.positions = molecule.conformers[0].value_in_unit(omm_unit.nanometer)
        interchange.box = [4, 4, 4]
        return interchange
    def test_residue_names_in_gro_file(self):
        """Test that residue names > 5 characters don't break .gro file output"""
        benzene = Molecule.from_file(get_test_file_path("benzene.sdf"))
        benzene.name = "supercalifragilisticexpialidocious"
        top = OFFBioTop.from_molecules(benzene)
        top.mdtop = md.Topology.from_openmm(top.to_openmm())

        # Populate an entire interchange because ...
        force_field = ForceField("openff-1.0.0.offxml")
        out = Interchange.from_smirnoff(force_field, top)
        out.box = [4, 4, 4]
        out.positions = benzene.conformers[0]

        # ... the easiest way to check the validity of the files
        # is to see if GROMACS can run them
        get_gromacs_energies(out)
示例#16
0
    def test_electrostatics_am1_handler(self):
        top = OFFBioTop.from_molecules(Molecule.from_smiles("C"))

        parameter_handlers = [
            ElectrostaticsHandler(version=0.3),
            ToolkitAM1BCCHandler(version=0.3),
        ]

        electrostatics_handler = SMIRNOFFElectrostaticsHandler._from_toolkit(
            parameter_handlers, top)

        np.testing.assert_allclose(
            [
                charge.m_as(unit.e)
                for charge in electrostatics_handler.charges.values()
            ],
            [-0.1088, 0.0267, 0.0267, 0.0267, 0.0267],
        )
    def test_from_parsley(self):

        force_field = ForceField("openff-1.3.0.offxml")

        top = OFFBioTop.from_molecules(
            [Molecule.from_smiles("CCO"),
             Molecule.from_smiles("CC")])

        out = Interchange.from_smirnoff(force_field, top)

        assert "Constraints" in out.handlers.keys()
        assert "Bonds" in out.handlers.keys()
        assert "Angles" in out.handlers.keys()
        assert "ProperTorsions" in out.handlers.keys()
        assert "vdW" in out.handlers.keys()

        assert type(out.topology) == OFFBioTop
        assert type(out.topology) != Topology
        assert isinstance(out.topology, Topology)
    def test_basic_combination(self, parsley_unconstrained):
        """Test basic use of Interchange.__add__() based on the README example"""
        mol = Molecule.from_smiles("C")
        mol.generate_conformers(n_conformers=1)
        top = OFFBioTop.from_molecules([mol])
        top.mdtop = md.Topology.from_openmm(top.to_openmm())

        openff_sys = Interchange.from_smirnoff(parsley_unconstrained, top)

        openff_sys.box = [4, 4, 4] * np.eye(3)
        openff_sys.positions = mol.conformers[0]._value / 10.0

        # Copy and translate atoms by [1, 1, 1]
        other = Interchange()
        other._inner_data = deepcopy(openff_sys._inner_data)
        other.positions += 1.0 * unit.nanometer

        combined = openff_sys + other

        # Just see if it can be converted into OpenMM and run
        get_openmm_energies(combined)
示例#19
0
    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,
            )
        )
示例#20
0
def _from_parmed(cls, structure) -> "Interchange":
    import parmed as pmd

    out = cls()

    if structure.positions:
        out.positions = np.asarray(structure.positions._value) * unit.angstrom

    if structure.box is not None:
        if any(structure.box[3:] != 3 * [90.0]):
            raise UnsupportedBoxError(
                f"Found box with angles {structure.box[3:]}. Only"
                "rectangular boxes are currently supported.")

        out.box = structure.box[:3] * unit.angstrom

    from openff.toolkit.topology import Molecule

    from openff.interchange.components.mdtraj import OFFBioTop

    if structure.topology is not None:
        mdtop = md.Topology.from_openmm(
            structure.topology)  # type: ignore[attr-defined]
        top = OFFBioTop(mdtop=mdtop)
        out.topology = top
    else:
        # TODO: Remove this case
        # This code should not be reached, since a pathway
        # OpenFF -> OpenMM -> MDTraj already exists

        mdtop = md.Topology()  # type: ignore[attr-defined]

        main_chain = md.core.topology.Chain(
            index=0, topology=mdtop)  # type: ignore[attr-defined]
        top = OFFBioTop(mdtop=None)

        # There is no way to tell if ParmEd residues are connected (cannot be processed
        # as separate OFFMols) or disconnected (can be). For now, will have to accept the
        # inefficiency of putting everything into on OFFMol ...

        mol = Molecule()
        mol.name = getattr(structure, "name", "Mol")

        for res in structure.residues:
            # ... however, MDTraj's Topology class only stores residues, not molecules,
            # so this should roughly match up with ParmEd
            this_res = md.core.topology.Residue(  # type: ignore[attr-defined]
                name=res.name,
                index=res.idx,
                chain=main_chain,
                resSeq=0,
            )

            for atom in res.atoms:
                mol.add_atom(atomic_number=atom.atomic_number,
                             formal_charge=0,
                             is_aromatic=False)
                mdtop.add_atom(
                    name=atom.name,
                    element=md.element.Element.getByAtomicNumber(
                        atom.element),  # type: ignore[attr-defined]
                    residue=this_res,
                )

            main_chain._residues.append(this_res)

        for res in structure.residues:
            for atom in res.atoms:
                for bond in atom.bonds:
                    try:
                        mol.add_bond(
                            atom1=bond.atom1.idx,
                            atom2=bond.atom2.idx,
                            bond_order=int(bond.order),
                            is_aromatic=False,
                        )
                    # TODO: Use a custom exception after
                    # https://github.com/openforcefield/openff-toolkit/issues/771
                    except Exception as e:
                        if "Bond already exists" in str(e):
                            pass
                        else:
                            raise e
                    mdtop.add_bond(
                        atom1=mdtop.atom(bond.atom1.idx),
                        atom2=mdtop.atom(bond.atom2.idx),
                        order=int(bond.order)
                        if bond.order is not None else None,
                    )

        # Topology.add_molecule requires a safe .to_smiles() call, so instead
        # do a dangerous molecule addition
        ref_mol = FrozenMolecule(mol)
        # This doesn't work because molecule hashing requires valid SMILES
        # top._reference_molecule_to_topology_molecules[ref_mol] = []
        # so just tack it on for now
        top._reference_mm_molecule = ref_mol
        top_mol = TopologyMolecule(reference_molecule=ref_mol, topology=top)
        top._topology_molecules.append(top_mol)
        # top._reference_molecule_to_topology_molecules[ref_mol].append(top_mol)
        mdtop._chains.append(main_chain)

    out.topology = top

    from openff.interchange.components.smirnoff import (
        SMIRNOFFAngleHandler,
        SMIRNOFFBondHandler,
        SMIRNOFFElectrostaticsHandler,
        SMIRNOFFImproperTorsionHandler,
        SMIRNOFFProperTorsionHandler,
        SMIRNOFFvdWHandler,
    )

    vdw_handler = SMIRNOFFvdWHandler()
    coul_handler = SMIRNOFFElectrostaticsHandler(method="pme")

    for atom in structure.atoms:
        atom_idx = atom.idx
        sigma = atom.sigma * unit.angstrom
        epsilon = atom.epsilon * kcal_mol
        charge = atom.charge * unit.elementary_charge
        top_key = TopologyKey(atom_indices=(atom_idx, ))
        pot_key = PotentialKey(id=str(atom_idx))
        pot = Potential(parameters={"sigma": sigma, "epsilon": epsilon})

        vdw_handler.slot_map.update({top_key: pot_key})
        vdw_handler.potentials.update({pot_key: pot})

        coul_handler.slot_map.update({top_key: pot_key})
        coul_handler.potentials.update(
            {pot_key: Potential(parameters={"charge": charge})})

    bond_handler = SMIRNOFFBondHandler()

    for bond in structure.bonds:
        atom1 = bond.atom1
        atom2 = bond.atom2
        k = bond.type.k * kcal_mol_a2
        length = bond.type.req * unit.angstrom
        top_key = TopologyKey(atom_indices=(atom1.idx, atom2.idx))
        pot_key = PotentialKey(id=f"{atom1.idx}-{atom2.idx}")
        pot = Potential(parameters={"k": k * 2, "length": length})

        bond_handler.slot_map.update({top_key: pot_key})
        bond_handler.potentials.update({pot_key: pot})

    out.handlers.update({"vdW": vdw_handler})
    out.handlers.update({"Electrostatics": coul_handler})
    out.handlers.update({"Bonds": bond_handler})

    angle_handler = SMIRNOFFAngleHandler()

    for angle in structure.angles:
        atom1 = angle.atom1
        atom2 = angle.atom2
        atom3 = angle.atom3
        k = angle.type.k * kcal_mol_rad2
        theta = angle.type.theteq * unit.degree
        top_key = TopologyKey(atom_indices=(atom1.idx, atom2.idx, atom3.idx))
        pot_key = PotentialKey(id=f"{atom1.idx}-{atom2.idx}-{atom3.idx}")
        pot = Potential(parameters={"k": k * 2, "angle": theta})

        angle_handler.slot_map.update({top_key: pot_key})
        angle_handler.potentials.update({pot_key: pot})

    proper_torsion_handler = SMIRNOFFProperTorsionHandler()
    improper_torsion_handler = SMIRNOFFImproperTorsionHandler()

    for dihedral in structure.dihedrals:
        if isinstance(dihedral.type, pmd.DihedralType):
            if dihedral.improper:
                _process_single_dihedral(dihedral, dihedral.type,
                                         improper_torsion_handler, 0)
            else:
                _process_single_dihedral(dihedral, dihedral.type,
                                         proper_torsion_handler, 0)
        elif isinstance(dihedral.type, pmd.DihedralTypeList):
            for dih_idx, dihedral_type in enumerate(dihedral.type):
                if dihedral.improper:
                    _process_single_dihedral(dihedral, dihedral_type,
                                             improper_torsion_handler, dih_idx)
                else:
                    _process_single_dihedral(
                        dihedral,
                        dihedral_type,
                        proper_torsion_handler,
                        dih_idx,
                    )

    out.handlers.update({"Electrostatics": coul_handler})
    out.handlers.update({"Bonds": bond_handler})
    out.handlers.update({"Angles": angle_handler})
    out.handlers.update({"ProperTorsions": proper_torsion_handler})

    return out
示例#21
0
 def ammonia_top(self):
     """Fixture that builds a simple ammonia topology"""
     mol = Molecule.from_smiles("N")
     top = OFFBioTop.from_molecules(4 * [mol])
     top.mdtop = md.Topology.from_openmm(top.to_openmm())
     return top
示例#22
0
    def from_smirnoff(
        cls,
        force_field: ForceField,
        topology: OFFBioTop,
        box=None,
    ) -> "Interchange":
        """Creates a new object by parameterizing a topology using the specified
        SMIRNOFF force field.

        Parameters
        ----------
        force_field
            The force field to parameterize the topology with.
        topology
            The topology to parameterize.
        box
            The box vectors associated with the interchange.

        Examples
        --------

        Generate an Interchange object from a single-molecule (OpenFF) topology and
        OpenFF 1.0.0 "Parsley"

        .. code-block:: pycon

            >>> from openff.interchange.components.interchange import Interchange
            >>> from openff.interchange.components.mdtraj import OFFBioTop
            >>> from openff.toolkit.topology import Molecule
            >>> from openff.toolkit.typing.engines.smirnoff import ForceField
            >>> import mdtraj as md
            >>> mol = Molecule.from_smiles("CC")
            >>> mol.generate_conformers(n_conformers=1)
            >>> top = OFFBioTop.from_molecules([mol])
            >>> top.mdtop = md.Topology.from_openmm(top.to_openmm())
            >>> parsley = ForceField("openff-1.0.0.offxml")
            >>> interchange = Interchange.from_smirnoff(topology=top, force_field=parsley)
            >>> interchange
            Interchange with 8 atoms, non-periodic topology

        """
        sys_out = Interchange()

        cls._check_supported_handlers(force_field)

        if isinstance(topology, OFFBioTop):
            sys_out.topology = topology
        elif isinstance(topology, Topology):
            sys_out.topology = OFFBioTop(other=topology)
            sys_out.topology.mdtop = md.Topology.from_openmm(
                topology.to_openmm())
        else:
            raise InvalidTopologyError(
                "Could not process topology argument, expected Topology or OFFBioTop. "
                f"Found object of type {type(topology)}.")

        parameter_handlers_by_type = {
            force_field[parameter_handler_name].__class__:
            force_field[parameter_handler_name]
            for parameter_handler_name in
            force_field.registered_parameter_handlers
        }

        if len(parameter_handlers_by_type) != len(
                force_field.registered_parameter_handlers):

            raise NotImplementedError(
                "Only force fields that contain one instance of each parameter handler "
                "type are currently supported.")

        for potential_handler_type in SMIRNOFF_POTENTIAL_HANDLERS:

            parameter_handlers = [
                parameter_handlers_by_type[allowed_type] for allowed_type in
                potential_handler_type.allowed_parameter_handlers()
                if allowed_type in parameter_handlers_by_type
            ]

            if len(parameter_handlers) == 0:
                continue

            # TODO: Might be simpler to rework the bond handler to be self-contained and
            #       move back to the constraint handler dealing with the logic (and
            #       depending on the bond handler)
            if potential_handler_type == SMIRNOFFBondHandler:
                SMIRNOFFBondHandler.check_supported_parameters(
                    force_field["Bonds"])
                potential_handler = SMIRNOFFBondHandler._from_toolkit(
                    parameter_handler=force_field["Bonds"],
                    topology=topology,
                    # constraint_handler=constraint_handler,
                )
                sys_out.handlers.update({"Bonds": potential_handler})
            elif potential_handler_type == SMIRNOFFConstraintHandler:
                bond_handler = force_field._parameter_handlers.get(
                    "Bonds", None)
                constraint_handler = force_field._parameter_handlers.get(
                    "Constraints", None)
                if constraint_handler is None:
                    continue
                constraints = SMIRNOFFConstraintHandler._from_toolkit(
                    parameter_handler=[
                        val for val in [bond_handler, constraint_handler]
                        if val is not None
                    ],
                    topology=topology,
                )
                sys_out.handlers.update({"Constraints": constraints})
                continue
            elif len(potential_handler_type.allowed_parameter_handlers()) > 1:
                potential_handler = potential_handler_type._from_toolkit(
                    parameter_handler=parameter_handlers,
                    topology=topology,
                )
            else:
                potential_handler_type.check_supported_parameters(
                    parameter_handlers[0])
                potential_handler = potential_handler_type._from_toolkit(  # type: ignore
                    parameter_handler=parameter_handlers[0],
                    topology=topology,
                )
            sys_out.handlers.update(
                {potential_handler.type: potential_handler})

        # `box` argument is only overriden if passed `None` and the input topology
        # has box vectors
        if box is None and topology.box_vectors is not None:
            sys_out.box = topology.box_vectors
        else:
            sys_out.box = box

        return sys_out
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,
        },
    )