예제 #1
0
def _write_atomtypes_buck(openff_sys: "Interchange", top_file: IO,
                          typemap: Dict):

    top_file.write("[ atomtypes ]\n")
    top_file.write(
        ";type, bondingtype, atomic_number, mass, charge, ptype, sigma, epsilon\n"
    )

    for atom_idx, atom_type in typemap.items():
        atom = openff_sys.topology.atom(atom_idx)
        element = ele.element_from_atomic_number(atom.atomic_number)
        parameters = _get_buck_parameters(openff_sys, atom_idx)
        a = parameters["A"].to(unit.Unit("kilojoule / mol")).magnitude
        b = parameters["B"].to(1 / unit.nanometer).magnitude
        c = parameters["C"].to(
            unit.Unit("kilojoule / mol * nanometer ** 6")).magnitude

        top_file.write(
            "{:<11s} {:6d} {:.16g} {:.16g} {:5s} {:.16g} {:.16g} {:.16g}".
            format(
                atom_type,  # atom type
                # "XX",  # atom "bonding type", i.e. bond class
                atom.atomic_number,
                element.mass,
                0.0,  # charge, overriden later in [ atoms ]
                "A",  # ptype
                a,
                b,
                c,
            ))
        top_file.write("\n")
예제 #2
0
def _from_omm_quantity(val: simtk_unit.Quantity):
    """Helper function to convert float or array quantities tagged with SimTK/OpenMM units to
    a Pint-compatible quantity"""
    unit_ = val.unit
    val_ = val.value_in_unit(unit_)
    if type(val_) in {float, int}:
        unit_ = val.unit
        return val_ * unit.Unit(str(unit_))
    elif type(val_) in {tuple, list, np.ndarray}:
        array = np.asarray(val_)
        return array * unit.Unit(str(unit_))
    else:
        raise UnitValidationError(
            "Found a simtk.unit.Unit wrapped around something other than a float-like "
            f"or np.ndarray-like. Found a unit wrapped around type {type(val_)}."
        )
예제 #3
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)
예제 #4
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)
예제 #5
0
def _write_angles(top_file: IO, openff_sys: "Interchange"):
    if "Angles" not in openff_sys.handlers.keys():
        return

    _store_bond_partners(openff_sys.topology.mdtop)

    top_file.write("[ angles ]\n")
    top_file.write("; ai\taj\tak\tfunc\tr\tk\n")

    angle_handler = openff_sys.handlers["Angles"]

    for angle in _iterate_angles(openff_sys.topology.mdtop):
        indices = (
            angle[0].index,
            angle[1].index,
            angle[2].index,
        )
        for top_key in angle_handler.slot_map:
            if top_key.atom_indices == indices:
                pot_key = angle_handler.slot_map[top_key]

        params = angle_handler.potentials[pot_key].parameters
        k = params["k"].m_as(unit.Unit("kilojoule / mole / radian ** 2"))
        theta = params["angle"].to(unit.degree).magnitude

        top_file.write("{:7d} {:7d} {:7d} {:4s} {:.16g} {:.16g}\n".format(
            indices[0] + 1,  # atom i
            indices[1] + 1,  # atom j
            indices[2] + 1,  # atom k
            str(1),  # angle type (functional form)
            theta,
            k,
        ))

    top_file.write("\n\n")
예제 #6
0
def _write_bonds(top_file: IO, openff_sys: "Interchange"):
    if "Bonds" not in openff_sys.handlers.keys():
        return

    top_file.write("[ bonds ]\n")
    top_file.write("; ai\taj\tfunc\tr\tk\n")

    bond_handler = openff_sys.handlers["Bonds"]

    for bond in openff_sys.topology.mdtop.bonds:

        indices = tuple(sorted((bond.atom1.index, bond.atom2.index)))
        for top_key in bond_handler.slot_map:
            if top_key.atom_indices == indices:
                pot_key = bond_handler.slot_map[top_key]
            elif top_key.atom_indices == indices[::-1]:
                pot_key = bond_handler.slot_map[top_key]

        params = bond_handler.potentials[pot_key].parameters

        k = params["k"].m_as(unit.Unit("kilojoule / mole / nanometer ** 2"))
        length = params["length"].to(unit.nanometer).magnitude

        top_file.write("{:7d} {:7d} {:4s} {:.16g} {:.16g}\n".format(
            indices[0] + 1,  # atom i
            indices[1] + 1,  # atom j
            str(1),  # bond type (functional form)
            length,
            k,
        ))

        del pot_key

    top_file.write("\n\n")
예제 #7
0
def _write_atomtypes_lj(openff_sys: "Interchange", top_file: IO,
                        typemap: Dict):

    top_file.write("[ atomtypes ]\n")
    top_file.write(
        ";type, bondingtype, atomic_number, mass, charge, ptype, sigma, epsilon\n"
    )

    for atom_idx, atom_type in typemap.items():
        atom = openff_sys.topology.mdtop.atom(atom_idx)
        mass = atom.element.mass
        atomic_number = atom.element.atomic_number
        parameters = _get_lj_parameters(openff_sys, atom_idx)
        sigma = parameters["sigma"].to(unit.nanometer).magnitude
        epsilon = parameters["epsilon"].to(
            unit.Unit("kilojoule / mole")).magnitude
        top_file.write(
            "{:<11s} {:6d} {:.16g} {:.16g} {:5s} {:.16g} {:.16g}".format(
                atom_type,  # atom type
                # "XX",  # atom "bonding type", i.e. bond class
                atomic_number,
                mass,
                0.0,  # charge, overriden later in [ atoms ]
                "A",  # ptype
                sigma,
                epsilon,
            ))
        top_file.write("\n")
예제 #8
0
def _write_pair_coeffs(lmp_file: IO, openff_sys: Interchange, atom_type_map: Dict):
    """Write the Pair Coeffs section of a LAMMPS data file"""
    lmp_file.write("Pair Coeffs\n\n")

    vdw_handler = openff_sys["vdW"]

    for atom_type_idx, smirks in atom_type_map.items():
        params = vdw_handler.potentials[smirks].parameters

        sigma = params["sigma"].to(unit.angstrom).magnitude
        epsilon = params["epsilon"].to(unit.Unit("kilocalorie / mole")).magnitude

        lmp_file.write(f"{atom_type_idx + 1:d}\t{epsilon:.8g}\t{sigma:.8g}\n")

    lmp_file.write("\n")
예제 #9
0
def _write_improper_coeffs(lmp_file: IO, openff_sys: Interchange):
    """Write the Improper Coeffs section of a LAMMPS data file"""
    lmp_file.write("\nImproper Coeffs\n\n")

    improper_handler = openff_sys.handlers["ImproperTorsions"]
    improper_type_map = dict(enumerate(improper_handler.potentials))

    for improper_type_idx, smirks in improper_type_map.items():
        params = improper_handler.potentials[smirks].parameters

        k = params["k"].to(unit.Unit("kilocalorie / mole")).magnitude
        n = int(params["periodicity"])
        phase = params["phase"].to(unit.degree).magnitude
        idivf = int(params["idivf"])
        k = k / idivf

        # See https://lammps.sandia.gov/doc/improper_cvff.html
        # E_periodic = k * (1 + cos(n * theta - phase))
        # E_cvff = k * (1 + d * cos(n * theta))
        # k_periodic = k_cvff
        # if phase = 0,
        #   d_cvff = 1
        #   n_periodic = n_cvff
        # if phase = 180,
        #   cos(n * x - pi) == - cos(n * x)
        #   d_cvff = -1
        #   n_periodic = n_cvff
        # k * (1 + cos(n * phi - pi / 2)) == k * (1 - cos(n * phi))

        if phase == 0:
            k_cvff = k
            d_cvff = 1
            n_cvff = n
        elif phase == 180:
            k_cvff = k
            d_cvff = -1
            n_cvff = n
        else:
            raise UnsupportedExportError(
                "Improper exports to LAMMPS are funky and not well-supported, the only compatibility"
                "found between periodidic impropers is with improper_style cvff when phase = 0 or 180 degrees"
            )

        lmp_file.write(
            f"{improper_type_idx+1:d} {k_cvff:.16g}\t{d_cvff:d}\t{n_cvff:.16g}\n"
        )

    lmp_file.write("\n")
예제 #10
0
def _write_angle_coeffs(lmp_file: IO, openff_sys: Interchange):
    """Write the Angle Coeffs section of a LAMMPS data file"""
    lmp_file.write("\nAngle Coeffs\n\n")

    angle_handler = openff_sys.handlers["Angles"]
    angle_type_map = dict(enumerate(angle_handler.potentials))

    for angle_type_idx, smirks in angle_type_map.items():
        params = angle_handler.potentials[smirks].parameters

        k = params["k"].to(unit.Unit("kilocalorie / mole / radian ** 2")).magnitude
        k = k * 0.5  # Account for LAMMPS wrapping 1/2 into k
        theta = params["angle"].to(unit.degree).magnitude

        lmp_file.write(f"{angle_type_idx+1:d} harmonic\t{k:.16g}\t{theta:.16g}\n")

    lmp_file.write("\n")
예제 #11
0
def _write_bond_coeffs(lmp_file: IO, openff_sys: Interchange):
    """Write the Bond Coeffs section of a LAMMPS data file"""
    lmp_file.write("Bond Coeffs\n\n")

    bond_handler = openff_sys.handlers["Bonds"]
    bond_type_map = dict(enumerate(bond_handler.potentials))

    for bond_type_idx, smirks in bond_type_map.items():
        params = bond_handler.potentials[smirks].parameters

        k = params["k"].to(unit.Unit("kilocalorie / mole / angstrom ** 2")).magnitude
        k = k * 0.5  # Account for LAMMPS wrapping 1/2 into k
        length = params["length"].to(unit.angstrom).magnitude

        lmp_file.write(f"{bond_type_idx+1:d} harmonic\t{k:.16g}\t{length:.16g}\n")

    lmp_file.write("\n")
예제 #12
0
def _write_proper_coeffs(lmp_file: IO, openff_sys: Interchange):
    """Write the Dihedral Coeffs section of a LAMMPS data file"""
    lmp_file.write("\nDihedral Coeffs\n\n")

    proper_handler = openff_sys.handlers["ProperTorsions"]
    proper_type_map = dict(enumerate(proper_handler.potentials))

    for proper_type_idx, smirks in proper_type_map.items():
        params = proper_handler.potentials[smirks].parameters

        k = params["k"].to(unit.Unit("kilocalorie / mole")).magnitude
        n = int(params["periodicity"])
        phase = params["phase"].to(unit.degree).magnitude
        idivf = int(params["idivf"])
        k = k / idivf

        lmp_file.write(
            f"{proper_type_idx+1:d} fourier 1\t{k:.16g}\t{n:d}\t{phase:.16g}\n"
        )

    lmp_file.write("\n")
예제 #13
0
from openff.interchange.models import PotentialKey, TopologyKey
from openff.interchange.tests import BaseTest
from openff.interchange.tests.utils import HAS_GROMACS, needs_gmx
from openff.interchange.utils import get_test_files_dir_path

if has_package("foyer"):
    import foyer

if HAS_GROMACS:
    from openff.interchange.drivers.gromacs import (
        _get_mdp_file,
        _run_gmx_energy,
        get_gromacs_energies,
    )

kj_mol = unit.Unit("kilojoule / mol")


@skip_if_missing("foyer")
class TestFoyer(BaseTest):
    @pytest.fixture(scope="session")
    def oplsaa(self):
        return foyer.forcefields.load_OPLSAA()

    @pytest.fixture(scope="session")
    def oplsaa_interchange_ethanol(self, oplsaa):

        molecule = Molecule.from_file(
            get_test_files_dir_path("foyer_test_molecules") + "/ethanol.sdf"
        )
        molecule.name = "ETH"
예제 #14
0
def _write_dihedrals(top_file: IO, openff_sys: "Interchange"):
    if "ProperTorsions" not in openff_sys.handlers:
        if "RBTorsions" not in openff_sys.handlers:
            if "ImproperTorsions" not in openff_sys.handlers:
                return

    _store_bond_partners(openff_sys.topology.mdtop)

    top_file.write("[ dihedrals ]\n")
    top_file.write(";    i      j      k      l   func\n")

    rb_torsion_handler = openff_sys.handlers.get("RBTorsions", [])
    proper_torsion_handler = openff_sys.handlers.get("ProperTorsions", [])
    improper_torsion_handler = openff_sys.handlers.get("ImproperTorsions", [])

    # TODO: Ensure number of torsions written matches what is expected
    for proper in _iterate_propers(openff_sys.topology.mdtop):
        if proper_torsion_handler:
            for top_key in proper_torsion_handler.slot_map:
                indices = tuple(a.index for a in proper)
                if top_key.atom_indices == indices:
                    pot_key = proper_torsion_handler.slot_map[top_key]
                    params = proper_torsion_handler.potentials[
                        pot_key].parameters

                    k = params["k"].to(unit.Unit("kilojoule / mol")).magnitude
                    periodicity = int(params["periodicity"])
                    phase = params["phase"].to(unit.degree).magnitude
                    idivf = int(params["idivf"]) if "idivf" in params else 1
                    top_file.write(
                        "{:7d} {:7d} {:7d} {:7d} {:6d} {:16g} {:16g} {:7d}\n".
                        format(
                            indices[0] + 1,
                            indices[1] + 1,
                            indices[2] + 1,
                            indices[3] + 1,
                            1,
                            phase,
                            k / idivf,
                            periodicity,
                        ))
        # This should be `if` if a single quartet can be subject to both proper and RB torsions
        if rb_torsion_handler:
            for top_key in rb_torsion_handler.slot_map:
                indices = tuple(a.index for a in proper)
                if top_key.atom_indices == indices:
                    pot_key = rb_torsion_handler.slot_map[top_key]
                    params = rb_torsion_handler.potentials[pot_key].parameters

                    c0 = params["C0"].to(
                        unit.Unit("kilojoule / mol")).magnitude
                    c1 = params["C1"].to(
                        unit.Unit("kilojoule / mol")).magnitude
                    c2 = params["C2"].to(
                        unit.Unit("kilojoule / mol")).magnitude
                    c3 = params["C3"].to(
                        unit.Unit("kilojoule / mol")).magnitude
                    c4 = params["C4"].to(
                        unit.Unit("kilojoule / mol")).magnitude
                    c5 = params["C5"].to(
                        unit.Unit("kilojoule / mol")).magnitude

                    top_file.write(
                        "{:7d} {:7d} {:7d} {:7d} {:6d} "
                        "{:16g} {:16g} {:16g} {:16g} {:16g} {:16g} \n".format(
                            indices[0] + 1,
                            indices[1] + 1,
                            indices[2] + 1,
                            indices[3] + 1,
                            3,
                            c0,
                            c1,
                            c2,
                            c3,
                            c4,
                            c5,
                        ))

    # TODO: Ensure number of torsions written matches what is expected
    for improper in _iterate_impropers(openff_sys.topology.mdtop):
        if improper_torsion_handler:
            for top_key in improper_torsion_handler.slot_map:
                indices = tuple(a.index for a in improper)
                if indices == top_key.atom_indices:
                    key = improper_torsion_handler.slot_map[top_key]
                    params = improper_torsion_handler.potentials[
                        key].parameters

                    k = params["k"].to(unit.Unit("kilojoule / mol")).magnitude
                    periodicity = int(params["periodicity"])
                    phase = params["phase"].to(unit.degree).magnitude
                    idivf = int(params["idivf"])
                    top_file.write(
                        "{:7d} {:7d} {:7d} {:7d} {:6d} {:.16g} {:.16g} {:.16g}\n"
                        .format(
                            indices[0] + 1,
                            indices[1] + 1,
                            indices[2] + 1,
                            indices[3] + 1,
                            4,
                            phase,
                            k / idivf,
                            periodicity,
                        ))
예제 #15
0
def _write_atoms(
    top_file: IO,
    openff_sys: "Interchange",
    typemap: Dict,
):
    """Write the [ atoms ] and [ pairs ] sections for a molecule"""
    top_file.write("[ atoms ]\n")
    top_file.write(";num, type, resnum, resname, atomname, cgnr, q, m\n")

    charges = openff_sys.handlers["Electrostatics"].charges

    for atom in openff_sys.topology.mdtop.atoms:
        atom_idx = atom.index
        mass = atom.element.mass
        atom_type = typemap[atom.index]
        res_idx = atom.residue.index
        res_name = str(atom.residue)
        top_key = TopologyKey(atom_indices=(atom_idx, ))
        charge = charges[top_key].m_as(unit.e)
        top_file.write("{:6d} {:18s} {:6d} {:8s} {:8s} {:6d} "
                       "{:18.8f} {:18.8f}\n".format(
                           atom_idx + 1,
                           atom_type,
                           res_idx + 1,
                           res_name,
                           atom_type,
                           atom_idx + 1,
                           charge,
                           mass,
                       ))

    top_file.write("[ pairs ]\n")
    top_file.write("; ai\taj\tfunct\n")

    _store_bond_partners(openff_sys.topology.mdtop)

    try:
        mixing_rule = openff_sys["vdW"].mixing_rule
        scale_lj = openff_sys["vdW"].scale_14
    except LookupError:
        mixing_rule = openff_sys["Buckingham-6"].mixing_rule
        scale_lj = openff_sys["Buckingham-6"].scale_14

    # Use a set to de-duplicate
    pairs: Set[Tuple] = {*_iterate_pairs(openff_sys.topology.mdtop)}
    for pair in pairs:
        indices = [a.index for a in pair]
        indices = sorted(indices)
        parameters1 = _get_lj_parameters(openff_sys, indices[0])
        sigma1 = parameters1["sigma"].to(unit.nanometer).magnitude
        epsilon1 = parameters1["epsilon"].to(
            unit.Unit("kilojoule / mole")).magnitude
        parameters2 = _get_lj_parameters(openff_sys, indices[1])
        sigma2 = parameters2["sigma"].to(unit.nanometer).magnitude
        epsilon2 = parameters2["epsilon"].to(
            unit.Unit("kilojoule / mole")).magnitude
        epsilon_mix = (epsilon1 * epsilon2)**0.5
        if mixing_rule == "lorentz-berthelot":
            sigma_mix = (sigma1 + sigma2) * 0.5
        elif mixing_rule == "geometric":
            sigma_mix = (sigma1 * sigma2)**0.5
        top_file.write("{:7d} {:7d} {:6d} {:16g} {:16g}\n".format(
            indices[0] + 1,
            indices[1] + 1,
            1,
            sigma_mix,
            epsilon_mix * scale_lj,
        ))
예제 #16
0
from openff.interchange.components.potentials import Potential
from openff.interchange.exceptions import UnsupportedBoxError, UnsupportedExportError
from openff.interchange.models import PotentialKey, TopologyKey

if TYPE_CHECKING:

    import parmed as pmd

    from openff.interchange.components.interchange import Interchange
    from openff.interchange.components.smirnoff import (
        SMIRNOFFImproperTorsionHandler,
        SMIRNOFFProperTorsionHandler,
    )

kcal_mol = unit.Unit("kilocalories / mol")
kcal_mol_a2 = unit.Unit("kilocalories / mol / angstrom ** 2")
kcal_mol_rad2 = unit.Unit("kilocalories / mol / rad ** 2")


def _to_parmed(off_system: "Interchange") -> "pmd.Structure":
    """Convert an OpenFF Interchange to a ParmEd Structure"""
    import parmed as pmd

    structure = pmd.Structure()
    _convert_box(off_system.box, structure)

    if "Electrostatics" in off_system.handlers.keys():
        has_electrostatics = True
        electrostatics_handler = off_system.handlers["Electrostatics"]
    else: