Beispiel #1
0
def _write_atoms(lmp_file: IO, openff_sys: System, atom_type_map: Dict):
    lmp_file.write("\nAtoms\n\n")

    molecule_map = dict(enumerate(
        openff_sys.topology.topology_molecules))  # type: ignore[union-attr]
    molecule_map_inv = dict({v: k for k, v in molecule_map.items()})

    atom_type_map_inv = dict({v: k for k, v in atom_type_map.items()})

    electrostatics_handler = openff_sys.handlers["Electrostatics"]
    vdw_hander = openff_sys.handlers["vdW"]

    for atom_idx, atom in enumerate(
            openff_sys.topology.topology_atoms):  # type: ignore[union-attr]

        molecule_idx = molecule_map_inv[atom.topology_molecule]

        top_key = TopologyKey(atom_indices=(atom_idx, ))
        pot_key = vdw_hander.slot_map[top_key]
        atom_type = atom_type_map_inv[pot_key]

        top_key = TopologyKey(atom_indices=(atom_idx, ))
        charge = electrostatics_handler.charges[
            top_key].magnitude  # type: ignore[attr-defined]
        pos = openff_sys.positions[atom_idx].to(unit.angstrom).magnitude
        lmp_file.write(
            "{:d}\t{:d}\t{:d}\t{:.8g}\t{:.8g}\t{:.8g}\t{:.8g}\n".format(
                atom_idx + 1,
                molecule_idx + 1,
                atom_type + 1,
                charge,
                pos[0],
                pos[1],
                pos[2],
            ))
Beispiel #2
0
def _get_buck_parameters(openff_sys: "System", atom_idx: int) -> Dict:
    buck_hander = openff_sys.handlers["Buckingham-6"]
    atom_key = TopologyKey(atom_indices=(atom_idx,))
    identifier = buck_hander.slot_map[atom_key]
    potential = buck_hander.potentials[identifier]
    parameters = potential.parameters

    return parameters
Beispiel #3
0
def _get_lj_parameters(openff_sys: "System", atom_idx: int) -> Dict:
    vdw_hander = openff_sys.handlers["vdW"]
    atom_key = TopologyKey(atom_indices=(atom_idx,))
    identifier = vdw_hander.slot_map[atom_key]
    potential = vdw_hander.potentials[identifier]
    parameters = potential.parameters

    return parameters
Beispiel #4
0
 def apply_library_charges(self,
                           library_charges: SMIRNOFFLibraryChargeHandler):
     for top_key, pot_key in library_charges.slot_map.items():
         ids = top_key.atom_indices
         charges = library_charges.potentials[pot_key].parameters["charges"]
         # Need to ensure this iterator follows ordering in force field
         for i, id_ in enumerate(ids):
             atom_key = TopologyKey(atom_indices=(id_, ))
             self.charges[atom_key] = charges[i]
Beispiel #5
0
    def test_force_field_basic_constraints(self, parsley):
        """Test that a force field Constraints tag adds a
        Constraints handler with expected data"""
        # TODO: Replace with more minimal force field

        top = Topology.from_molecules(Molecule.from_smiles("CC"))
        sys_out = parsley.create_openff_system(top)

        assert "Constraints" in sys_out.handlers.keys()
        constraints = sys_out.handlers["Constraints"]
        c_c_bond = TopologyKey(atom_indices=(0, 1))  # C-C bond
        assert c_c_bond not in constraints.slot_map.keys()
        c_h_bond = TopologyKey(atom_indices=(0, 2))  # C-H bond
        assert c_h_bond in constraints.slot_map.keys()
        assert len(constraints.slot_map.keys()) == 6  # number of C-H bonds
        assert len({constraints.slot_map.values()}) == 1  # always True
        assert ("distance" in constraints.constraints[
            constraints.slot_map[c_h_bond]].parameters)
Beispiel #6
0
 def apply_charge_increments(
         self, charge_increments: SMIRNOFFChargeIncrementHandler):
     for top_key, pot_key in charge_increments.slot_map.items():
         ids = top_key.atom_indices
         charges = charge_increments.potentials[pot_key].parameters[
             "charge_increments"]
         for i, id_ in enumerate(ids):
             atom_key = TopologyKey(atom_indices=(id_, ))
             self.charges[atom_key] += charges[i]
Beispiel #7
0
 def store_matches(
     self,
     parameter_handler: ChargeIncrementModelHandler,
     topology: Topology,
 ) -> None:
     matches = parameter_handler.find_matches(topology)
     for key, val in matches.items():
         top_key = TopologyKey(atom_indices=key)
         pot_key = PotentialKey(id=val.parameter_type.smirks)
         self.slot_map[top_key] = pot_key
Beispiel #8
0
    def store_matches(self, parameter_handler: AngleHandler,
                      topology: Topology) -> None:
        """
        Populate self.slot_map with key-val pairs of slots
        and unique potential identifiers

        """
        matches = parameter_handler.find_matches(topology)
        for key, val in matches.items():
            topology_key = TopologyKey(atom_indices=key)
            potential_key = PotentialKey(id=val.parameter_type.smirks)
            self.slot_map[topology_key] = potential_key
Beispiel #9
0
    def store_matches(self, parameter_handler: ProperTorsionHandler,
                      topology: Topology) -> None:
        """
        Populate self.slot_map with key-val pairs of slots
        and unique potential identifiers

        """
        matches = parameter_handler.find_matches(topology)
        for key, val in matches.items():
            n_terms = len(val.parameter_type.k)
            for n in range(n_terms):
                smirks = val.parameter_type.smirks
                topology_key = TopologyKey(atom_indices=key, mult=n)
                potential_key = PotentialKey(id=smirks, mult=n)
                self.slot_map[topology_key] = potential_key
Beispiel #10
0
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
Beispiel #11
0
    def store_charges(
        self,
        forcefield: ForceField,
        topology: Topology,
    ) -> None:
        """
        Populate self.slot_map with key-val pairs of slots
        and unique potential identifiers

        """
        self.method = forcefield["Electrostatics"].method

        partial_charges = get_partial_charges_from_openmm_system(
            forcefield.create_openmm_system(topology=topology))

        for i, charge in enumerate(partial_charges):
            topology_key = TopologyKey(atom_indices=(i, ))
            self.charge_map[topology_key] = charge * unit.elementary_charge
Beispiel #12
0
def _write_bonds(lmp_file: IO, openff_sys: System):
    lmp_file.write("\nBonds\n\n")

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

    bond_type_map_inv = dict({v: k for k, v in bond_type_map.items()})

    for bond_idx, bond in enumerate(
            openff_sys.topology.topology_bonds):  # type: ignore[union-attr]
        # These are "topology indices"
        indices = tuple(sorted(a.topology_atom_index for a in bond.atoms))
        top_key = TopologyKey(atom_indices=indices)
        pot_key = bond_handler.slot_map[top_key]
        bond_type = bond_type_map_inv[pot_key]

        lmp_file.write("{:d}\t{:d}\t{:d}\t{:d}\n".format(
            bond_idx + 1,
            bond_type + 1,
            indices[0] + 1,
            indices[1] + 1,
        ))
Beispiel #13
0
def _write_atoms(
    top_file: IO,
    mol_name: str,
    mol_data: Dict,
    off_sys: "System",
    typemap: Dict,
):
    """Write the [ atoms ] section for a molecule"""
    top_file.write("[ atoms ]\n")
    top_file.write(";num, type, resnum, resname, atomname, cgnr, q, m\n")

    ref_mol = mol_data["reference_molecule"]
    top_mol = off_sys.topology._reference_molecule_to_topology_molecules[ref_mol][0]  # type: ignore

    offset = top_mol.atom_start_topology_index

    for atom_idx, atom in enumerate(top_mol.atoms):
        atom_top_idx = atom_idx + offset
        atom_type = typemap[atom_top_idx]
        element = ele.element_from_atomic_number(atom.atomic_number)
        mass = element.mass
        top_key = TopologyKey(atom_indices=(atom_top_idx,))
        charge = (
            off_sys.handlers["Electrostatics"].charges[top_key].magnitude  # type: ignore
        )
        top_file.write(
            "{:6d} {:18s} {:6d} {:8s} {:8s} {:6d} "
            "{:18.8f} {:18.8f}\n".format(
                atom_idx + 1,
                atom_type,
                1,  # residue_index, always 1 while writing out per mol
                mol_name,  # residue_name,
                element.symbol,
                atom_idx + 1,  # cgnr
                charge,
                mass,
            )
        )
Beispiel #14
0
    def cache_charges(self, partial_charge_method: str, topology: Topology):

        charges: Dict[TopologyKey, FloatQuantity] = dict()

        for ref_mol in topology.reference_molecules:
            ref_mol.assign_partial_charges(
                partial_charge_method=partial_charge_method)

            for top_mol in topology._reference_molecule_to_topology_molecules[
                    ref_mol]:
                for topology_particle in top_mol.atoms:
                    ref_mol_particle_index = (
                        topology_particle.atom.molecule_particle_index)
                    topology_particle_index = topology_particle.topology_particle_index
                    partial_charge = ref_mol._partial_charges[
                        ref_mol_particle_index]
                    partial_charge = partial_charge / omm_unit.elementary_charge
                    partial_charge = partial_charge * unit.elementary_charge
                    top_key = TopologyKey(
                        atom_indices=(topology_particle_index, ))
                    charges[top_key] = partial_charge

        self.cache[partial_charge_method] = charges
Beispiel #15
0
def _write_angles(lmp_file: IO, openff_sys: System):
    lmp_file.write("\nAngles\n\n")

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

    angle_type_map_inv = dict({v: k for k, v in angle_type_map.items()})

    for angle_idx, angle in enumerate(
            openff_sys.topology.angles):  # type: ignore[union-attr]
        # These are "topology indices"
        indices = tuple(a.topology_atom_index for a in angle)
        top_key = TopologyKey(atom_indices=indices)
        pot_key = angle_handler.slot_map[top_key]
        angle_type = angle_type_map_inv[pot_key]

        lmp_file.write("{:d}\t{:d}\t{:d}\t{:d}\t{:d}\n".format(
            angle_idx + 1,
            angle_type + 1,
            indices[0] + 1,
            indices[1] + 1,
            indices[2] + 1,
        ))
Beispiel #16
0
    def test_angle_potential_handler(self):
        top = Topology.from_molecules(Molecule.from_smiles("CCC"))

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

        from openff.system.stubs import ForceField

        forcefield = ForceField()
        forcefield.register_parameter_handler(angle_handler)
        angle_potentials = forcefield["Angles"].create_potential(top)

        top_key = TopologyKey(atom_indices=(0, 1, 2))
        pot = angle_potentials.potentials[angle_potentials.slot_map[top_key]]

        kcal_mol_rad2 = unit.Unit("kilocalorie / (mole * radian ** 2)")
        assert pot.parameters["k"].to(
            kcal_mol_rad2).magnitude == pytest.approx(2.5)
Beispiel #17
0
    def test_bond_potential_handler(self):
        top = Topology.from_molecules(Molecule.from_smiles("O=O"))

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

        from openff.system.stubs import ForceField

        forcefield = ForceField()
        forcefield.register_parameter_handler(bond_handler)
        bond_potentials = forcefield["Bonds"].create_potential(top)

        top_key = TopologyKey(atom_indices=(0, 1))
        pot = bond_potentials.potentials[bond_potentials.slot_map[top_key]]

        kcal_mol_a2 = unit.Unit("kilocalorie / (angstrom ** 2 * mole)")
        assert pot.parameters["k"].to(kcal_mol_a2).magnitude == pytest.approx(
            1.5)
Beispiel #18
0
def from_parmed(cls) -> "System":

    from openff.system.components.system import System

    out = System()

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

    if any(cls.box[3:] != 3 * [90.0]):
        from openff.system.exceptions import UnsupportedBoxError

        raise UnsupportedBoxError(f"Found box with angles {cls.box[3:]}. Only"
                                  "rectangular boxes are currently supported.")

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

    from openff.toolkit.topology import Molecule, Topology

    top = Topology()

    for res in cls.residues:
        mol = Molecule()
        mol.name = res.name
        for atom in res.atoms:
            mol.add_atom(atomic_number=atom.atomic_number,
                         formal_charge=0,
                         is_aromatic=False)
        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

        top.add_molecule(mol)

    out.topology = top

    from openff.system.components.smirnoff import (
        ElectrostaticsMetaHandler,
        SMIRNOFFAngleHandler,
        SMIRNOFFBondHandler,
        SMIRNOFFImproperTorsionHandler,
        SMIRNOFFProperTorsionHandler,
        SMIRNOFFvdWHandler,
    )

    vdw_handler = SMIRNOFFvdWHandler()
    coul_handler = ElectrostaticsMetaHandler()

    for atom in cls.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.charges.update({top_key: charge})

    bond_handler = SMIRNOFFBondHandler()

    for bond in cls.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})  # type: ignore[dict-item]
    out.handlers.update({"Bonds": bond_handler})

    angle_handler = SMIRNOFFAngleHandler()

    for angle in cls.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 cls.dihedrals:
        atom1 = dihedral.atom1
        atom2 = dihedral.atom2
        atom3 = dihedral.atom3
        atom4 = dihedral.atom4
        k = dihedral.type.phi_k * kcal_mol_rad2
        periodicity = dihedral.type.per * unit.dimensionless
        phase = dihedral.type.phase * unit.degree
        if dihedral.improper:
            # ParmEd stores the central atom _third_ (AMBER style)
            # SMIRNOFF stores the central atom _second_
            # https://parmed.github.io/ParmEd/html/topobj/parmed.topologyobjects.Dihedral.html#parmed-topologyobjects-dihedral
            # https://open-forcefield-toolkit.readthedocs.io/en/latest/smirnoff.html#impropertorsions
            top_key = TopologyKey(
                atom_indices=(atom1.idx, atom2.idx, atom2.idx, atom4.idx),
                mult=1,
            )
            pot_key = PotentialKey(
                id=f"{atom1.idx}-{atom3.idx}-{atom2.idx}-{atom4.idx}",
                mult=1,
            )
            pot = Potential(parameters={
                "k": k,
                "periodicity": periodicity,
                "phase": phase
            })

            while pot_key in improper_torsion_handler.potentials:
                pot_key.mult += 1  # type: ignore[operator]
                top_key.mult += 1  # type: ignore[operator]

            improper_torsion_handler.slot_map.update({top_key: pot_key})
            improper_torsion_handler.potentials.update({pot_key: pot})
        else:
            top_key = TopologyKey(
                atom_indices=(atom1.idx, atom2.idx, atom3.idx, atom4.idx),
                mult=1,
            )
            pot_key = PotentialKey(
                id=f"{atom1.idx}-{atom2.idx}-{atom3.idx}-{atom4.idx}",
                mult=1,
            )
            pot = Potential(parameters={
                "k": k,
                "periodicity": periodicity,
                "phase": phase
            })

            while pot_key in proper_torsion_handler.potentials:
                pot_key.mult += 1  # type: ignore[operator]
                top_key.mult += 1  # type: ignore[operator]

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

    out.handlers.update({"Electrostatics":
                         coul_handler})  # type: ignore[dict-item]
    out.handlers.update({"Bonds": bond_handler})
    out.handlers.update({"Angles": angle_handler})
    out.handlers.update({"ProperTorsions": proper_torsion_handler})

    return out
Beispiel #19
0
def to_parmed(off_system: "System") -> pmd.Structure:
    """Convert an OpenFF System to a ParmEd Structure"""
    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:
        has_electrostatics = False

    for topology_molecule in off_system.topology.topology_molecules:  # type: ignore[union-attr]
        for atom in topology_molecule.atoms:
            atomic_number = atom.atomic_number
            element = pmd.periodic_table.Element[atomic_number]
            mass = pmd.periodic_table.Mass[element]
            structure.add_atom(
                pmd.Atom(
                    atomic_number=atomic_number,
                    mass=mass,
                ),
                resname="FOO",
                resnum=0,
            )

    if "Bonds" in off_system.handlers.keys():
        bond_handler = off_system.handlers["Bonds"]
        bond_type_map: Dict = dict()
        for pot_key, pot in bond_handler.potentials.items():
            k = pot.parameters["k"].to(kcal_mol_a2).magnitude / 2
            length = pot.parameters["length"].to(unit.angstrom).magnitude
            bond_type = pmd.BondType(k=k, req=length)
            bond_type_map[pot_key] = bond_type
            structure.bond_types.append(bond_type)

        for top_key, pot_key in bond_handler.slot_map.items():
            idx_1, idx_2 = top_key.atom_indices
            bond_type = bond_type_map[pot_key]
            bond = pmd.Bond(
                atom1=structure.atoms[idx_1],
                atom2=structure.atoms[idx_2],
                type=bond_type,
            )
            structure.bonds.append(bond)

    structure.bond_types.claim()

    if "Angles" in off_system.handlers.keys():
        angle_handler = off_system.handlers["Angles"]
        angle_type_map: Dict = dict()
        for pot_key, pot in angle_handler.potentials.items():
            k = pot.parameters["k"].to(kcal_mol_rad2).magnitude / 2
            theta = pot.parameters["angle"].to(unit.degree).magnitude
            # TODO: Look up if AngleType already exists in struct
            angle_type = pmd.AngleType(k=k, theteq=theta)
            angle_type_map[pot_key] = angle_type
            structure.angle_types.append(angle_type)

        for top_key, pot_key in angle_handler.slot_map.items():
            idx_1, idx_2, idx_3 = top_key.atom_indices
            angle_type = angle_type_map[pot_key]
            structure.angles.append(
                pmd.Angle(
                    atom1=structure.atoms[idx_1],
                    atom2=structure.atoms[idx_2],
                    atom3=structure.atoms[idx_3],
                    type=angle_type,
                ))
            structure.angle_types.append(angle_type)

    structure.angle_types.claim()

    # ParmEd treats 1-4 scaling factors at the level of each DihedralType,
    # whereas SMIRNOFF captures them at the level of the non-bonded handler,
    # so they need to be stored here for processing dihedrals
    vdw_14 = off_system.handlers["vdW"].scale_14  # type: ignore[attr-defined]
    if has_electrostatics:
        coul_14 = off_system.handlers[
            "Electrostatics"].scale_14  # type: ignore[attr-defined]
    else:
        coul_14 = 1.0
    vdw_handler = off_system.handlers["vdW"]
    if "ProperTorsions" in off_system.handlers.keys():
        proper_torsion_handler = off_system.handlers["ProperTorsions"]
        proper_type_map: Dict = dict()
        for pot_key, pot in proper_torsion_handler.potentials.items():
            k = pot.parameters["k"].to(kcal_mol).magnitude
            periodicity = pot.parameters["periodicity"]
            phase = pot.parameters["phase"].magnitude
            proper_type = pmd.DihedralType(
                phi_k=k,
                per=periodicity,
                phase=phase,
                scnb=1 / vdw_14,
                scee=1 / coul_14,
            )
            proper_type_map[pot_key] = proper_type
            structure.dihedral_types.append(proper_type)

        for top_key, pot_key in proper_torsion_handler.slot_map.items():
            idx_1, idx_2, idx_3, idx_4 = top_key.atom_indices
            dihedral_type = proper_type_map[pot_key]
            structure.dihedrals.append(
                pmd.Dihedral(
                    atom1=structure.atoms[idx_1],
                    atom2=structure.atoms[idx_2],
                    atom3=structure.atoms[idx_3],
                    atom4=structure.atoms[idx_4],
                    type=dihedral_type,
                ))
            structure.dihedral_types.append(dihedral_type)

            key1 = TopologyKey(atom_indices=(idx_1, ))
            key4 = TopologyKey(atom_indices=(idx_4, ))
            vdw1 = vdw_handler.potentials[vdw_handler.slot_map[key1]]
            vdw4 = vdw_handler.potentials[vdw_handler.slot_map[key4]]
            sig1, eps1 = _lj_params_from_potential(vdw1)
            sig4, eps4 = _lj_params_from_potential(vdw4)
            sig = (sig1 + sig4) * 0.5
            eps = (eps1 * eps4)**0.5
            nbtype = pmd.NonbondedExceptionType(rmin=sig * 2**(1 / 6),
                                                epsilon=eps * vdw_14,
                                                chgscale=coul_14)
            structure.adjusts.append(
                pmd.NonbondedException(structure.atoms[idx_1],
                                       structure.atoms[idx_4],
                                       type=nbtype))
            structure.adjust_types.append(nbtype)

    structure.dihedral_types.claim()
    structure.adjust_types.claim()

    #    if False:  # "ImroperTorsions" in off_system.term_collection.terms:
    #        improper_term = off_system.term_collection.terms["ImproperTorsions"]
    #        for improper, smirks in improper_term.smirks_map.items():
    #            idx_1, idx_2, idx_3, idx_4 = improper
    #            pot = improper_term.potentials[improper_term.smirks_map[improper]]
    #            # TODO: Better way of storing periodic data in generally, probably need to improve Potential
    #            n = re.search(r"\d", "".join(pot.parameters.keys())).group()
    #            k = pot.parameters["k" + n].m  # kcal/mol
    #            periodicity = pot.parameters["periodicity" + n].m  # dimless
    #            phase = pot.parameters["phase" + n].m  # degree
    #
    #            dihedral_type = pmd.DihedralType(per=periodicity, phi_k=k, phase=phase)
    #            structure.dihedrals.append(
    #                pmd.Dihedral(
    #                    atom1=structure.atoms[idx_1],
    #                    atom2=structure.atoms[idx_2],
    #                    atom3=structure.atoms[idx_3],
    #                    atom4=structure.atoms[idx_4],
    #                    type=dihedral_type,
    #                )
    #            )

    vdw_handler = off_system.handlers["vdW"]
    for pmd_idx, pmd_atom in enumerate(structure.atoms):
        top_key = TopologyKey(atom_indices=(pmd_idx, ))
        smirks = vdw_handler.slot_map[top_key]
        potential = vdw_handler.potentials[smirks]
        element = pmd.periodic_table.Element[pmd_atom.element]
        sigma, epsilon = _lj_params_from_potential(potential)

        atom_type = pmd.AtomType(
            name=element + str(pmd_idx + 1),
            number=pmd_idx,
            atomic_number=pmd_atom.atomic_number,
            mass=pmd.periodic_table.Mass[element],
        )

        atom_type.set_lj_params(eps=epsilon, rmin=sigma * 2**(1 / 6) / 2)
        pmd_atom.atom_type = atom_type
        pmd_atom.type = atom_type.name
        pmd_atom.name = pmd_atom.type

    for pmd_idx, pmd_atom in enumerate(structure.atoms):
        if has_electrostatics:
            top_key = TopologyKey(atom_indices=(pmd_idx, ))
            partial_charge = electrostatics_handler.charges[
                top_key]  # type: ignore[attr-defined]
            unitless_ = partial_charge.to(unit.elementary_charge).magnitude
            pmd_atom.charge = float(unitless_)
            pmd_atom.atom_type.charge = float(unitless_)
        else:
            pmd_atom.charge = 0

    # Assign dummy residue names, GROMACS will not accept empty strings
    for res in structure.residues:
        res.name = "FOO"

    structure.positions = off_system.positions.to(
        unit.angstrom).magnitude  # type: ignore[attr-defined]
    for idx, pos in enumerate(structure.positions):
        structure.atoms[idx].xx = pos._value[0]
        structure.atoms[idx].xy = pos._value[1]
        structure.atoms[idx].xz = pos._value[2]

    return structure
Beispiel #20
0
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