예제 #1
0
def _write_bonds(lmp_file: IO, openff_sys: Interchange):
    """Write the Bonds section of a LAMMPS data file"""
    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.mdtop.bonds):
        # These are "topology indices"
        indices = (
            bond.atom1.index,
            bond.atom2.index,
        )
        top_key = TopologyKey(atom_indices=indices)
        if top_key in bond_handler.slot_map:
            pot_key = bond_handler.slot_map[top_key]
        else:
            top_key = TopologyKey(atom_indices=indices[::-1])
            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,
            )
        )
예제 #2
0
def _process_single_dihedral(
    dihedral: "pmd.Dihedral",
    dihedral_type: "pmd.DihedralType",
    handler: Union["SMIRNOFFImproperTorsionHandler",
                   "SMIRNOFFProperTorsionHandler"],
    mult: Optional[int] = None,
):
    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=mult,
        )
        pot_key = PotentialKey(
            id=f"{atom1.idx}-{atom3.idx}-{atom2.idx}-{atom4.idx}",
            mult=mult,
        )
        pot = Potential(parameters={
            "k": k,
            "periodicity": periodicity,
            "phase": phase
        })

        if pot_key in handler.potentials:
            raise Exception("fudging dihedral indices")

        handler.slot_map.update({top_key: pot_key})
        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 handler.potentials:
            pot_key.mult += 1  # type: ignore[operator]
            top_key.mult += 1  # type: ignore[operator]

        handler.slot_map.update({top_key: pot_key})
        handler.potentials.update({pot_key: pot})
예제 #3
0
    def test_store_improper_torsion_matches(self):
        formaldehyde: Molecule = Molecule.from_mapped_smiles(
            "[H:3][C:1]([H:4])=[O:2]")

        parameter_handler = ImproperTorsionHandler(version=0.3)
        parameter_handler.add_parameter(
            parameter=ImproperTorsionHandler.ImproperTorsionType(
                smirks="[*:1]~[#6X3:2](~[*:3])~[*:4]",
                periodicity1=2,
                phase1=180.0 * simtk_unit.degree,
                k1=1.1 * simtk_unit.kilocalorie_per_mole,
            ))

        potential_handler = SMIRNOFFImproperTorsionHandler()
        potential_handler.store_matches(parameter_handler,
                                        formaldehyde.to_topology())

        assert len(potential_handler.slot_map) == 3

        assert (TopologyKey(atom_indices=(0, 1, 2, 3), mult=0)
                in potential_handler.slot_map)
        assert (TopologyKey(atom_indices=(0, 2, 3, 1), mult=0)
                in potential_handler.slot_map)
        assert (TopologyKey(atom_indices=(0, 3, 1, 2), mult=0)
                in potential_handler.slot_map)
예제 #4
0
    def store_matches(
        self,
        parameter_handler: Union["ElectrostaticsHandlerType",
                                 List["ElectrostaticsHandlerType"]],
        topology: Union["Topology", "OFFBioTop"],
    ) -> None:
        """
        Populate self.slot_map with key-val pairs of slots
        and unique potential identifiers
        """

        # Reshape the parameter handlers into a dictionary for easier referencing.
        parameter_handlers = {
            handler._TAGNAME: handler
            for handler in (parameter_handler if isinstance(
                parameter_handler, list) else [parameter_handler])
        }

        self.potentials = dict()
        self.slot_map = dict()

        reference_molecules = [*topology.reference_molecules]

        for reference_molecule in reference_molecules:

            matches, potentials = self._find_reference_matches(
                parameter_handlers, reference_molecule)

            match_mults = defaultdict(set)

            for top_key in matches:
                match_mults[top_key.atom_indices].add(top_key.mult)

            self.potentials.update(potentials)

            for top_mol in topology._reference_molecule_to_topology_molecules[
                    reference_molecule]:

                for topology_particle in top_mol.atoms:

                    reference_index = topology_particle.atom.molecule_particle_index
                    topology_index = topology_particle.topology_particle_index

                    for mult in match_mults[(reference_index, )]:

                        top_key = TopologyKey(atom_indices=(topology_index, ),
                                              mult=mult)

                        self.slot_map[top_key] = matches[TopologyKey(
                            atom_indices=(reference_index, ), mult=mult)]
예제 #5
0
def _write_angles(lmp_file: IO, openff_sys: Interchange):
    """Write the Angles section of a LAMMPS data file"""
    from openff.interchange.components.mdtraj import (
        _iterate_angles,
        _store_bond_partners,
    )

    _store_bond_partners(openff_sys.topology.mdtop)

    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(_iterate_angles(openff_sys.topology.mdtop)):
        # These are "topology indices"
        indices = tuple(a.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,
            )
        )
예제 #6
0
def _convert_periodic_torsion_force(force):
    # TODO: Can impropers be separated out from a PeriodicTorsionForce?
    # Maybe by seeing if a quartet is in mol/top.propers or .impropers
    from openff.interchange.components.smirnoff import SMIRNOFFProperTorsionHandler

    proper_torsion_handler = SMIRNOFFProperTorsionHandler()

    n_parametrized_torsions = force.getNumTorsions()

    for idx in range(n_parametrized_torsions):
        atom1, atom2, atom3, atom4, per, phase, k = force.getTorsionParameters(
            idx)
        # TODO: Process layered torsions
        top_key = TopologyKey(atom_indices=(atom1, atom2, atom3, atom4),
                              mult=0)
        while top_key in proper_torsion_handler.slot_map:
            top_key.mult += 1

        pot_key = PotentialKey(id=f"{atom1}-{atom2}-{atom3}-{atom4}",
                               mult=top_key.mult)
        pot = Potential(
            parameters={
                "periodicity": int(per) * unit.dimensionless,
                "phase": from_simtk(phase),
                "k": from_simtk(k),
                "idivf": 1 * unit.dimensionless,
            })

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

    return proper_torsion_handler
예제 #7
0
def _write_atoms(lmp_file: IO, openff_sys: Interchange, atom_type_map: Dict):
    """Write the Atoms section of a LAMMPS data file"""
    lmp_file.write("\nAtoms\n\n")

    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"]

    charges = electrostatics_handler.charges

    for atom in openff_sys.topology.mdtop.atoms:

        molecule_idx = atom.residue.index

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

        charge = charges[top_key].m_as(unit.e)
        pos = openff_sys.positions[atom.index].to(unit.angstrom).magnitude
        lmp_file.write(
            "{:d}\t{:d}\t{:d}\t{:.8g}\t{:.8g}\t{:.8g}\t{:.8g}\n".format(
                atom.index + 1,
                molecule_idx + 1,
                atom_type + 1,
                charge,
                pos[0],
                pos[1],
                pos[2],
            )
        )
예제 #8
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)
예제 #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 _charge_increment_to_potentials(
        cls,
        atom_indices: Tuple[int, ...],
        parameter: ChargeIncrementModelHandler.ChargeIncrementType,
    ) -> Tuple[Dict[TopologyKey, PotentialKey], Dict[PotentialKey, Potential]]:
        """Maps a matched charge increment parameter to a set of potentials."""

        matches = {}
        potentials = {}

        for i, atom_index in enumerate(atom_indices):
            topology_key = TopologyKey(atom_indices=(atom_index, ))
            potential_key = PotentialKey(
                id=parameter.smirks,
                mult=i,
                associated_handler="ChargeIncrementModel")

            # TODO: Handle the cases where n - 1 charge increments have been defined,
            #       maybe by implementing this in the TK?
            charge_increment = getattr(parameter, f"charge_increment{i + 1}")

            potential = Potential(
                parameters={"charge_increment": from_simtk(charge_increment)})

            matches[topology_key] = potential_key
            potentials[potential_key] = potential

        return matches, potentials
예제 #11
0
    def store_matches(
        self,
        parameter_handler: ParameterHandler,
        topology: Union["Topology", "OFFBioTop"],
    ) -> None:
        """
        Populate self.slot_map with key-val pairs of slots
        and unique potential identifiers

        """
        parameter_handler_name = getattr(parameter_handler, "_TAGNAME", None)
        if self.slot_map:
            # TODO: Should the slot_map always be reset, or should we be able to partially
            # update it? Also Note the duplicated code in the child classes
            self.slot_map = dict()
        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,
                associated_handler=parameter_handler_name)
            self.slot_map[topology_key] = potential_key

        if self.__class__.__name__ in [
                "SMIRNOFFBondHandler", "SMIRNOFFAngleHandler"
        ]:
            valence_terms = self.valence_terms(topology)

            parameter_handler._check_all_valence_terms_assigned(
                assigned_terms=matches,
                valence_terms=valence_terms,
                exception_cls=UnassignedValenceParameterException,
            )
예제 #12
0
    def store_matches(
        self,
        parameter_handler: "ProperTorsionHandler",
        topology: "OFFBioTop",
    ) -> None:
        """
        Populate self.slot_map with key-val pairs of slots
        and unique potential identifiers

        """
        if self.slot_map:
            self.slot_map = dict()
        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, associated_handler="ProperTorsions")
                self.slot_map[topology_key] = potential_key

        parameter_handler._check_all_valence_terms_assigned(
            assigned_terms=matches,
            valence_terms=list(topology.propers),
            exception_cls=UnassignedProperTorsionParameterException,
        )
예제 #13
0
    def _find_am1_matches(
        cls,
        parameter_handler: Union["ToolkitAM1BCCHandler",
                                 ChargeIncrementModelHandler],
        reference_molecule: Molecule,
    ) -> Tuple[Dict[TopologyKey, PotentialKey], Dict[PotentialKey, Potential]]:
        """Constructs a slot and potential map for a charge model based parameter handler."""

        reference_molecule = copy.deepcopy(reference_molecule)
        reference_smiles = reference_molecule.to_smiles(
            isomeric=True, explicit_hydrogens=True, mapped=True)

        method = getattr(parameter_handler, "partial_charge_method", "am1bcc")

        partial_charges = cls._compute_partial_charges(reference_molecule,
                                                       method=method)

        matches = {}
        potentials = {}

        for i, partial_charge in enumerate(partial_charges):

            potential_key = PotentialKey(id=reference_smiles,
                                         mult=i,
                                         associated_handler="ToolkitAM1BCC")
            potentials[potential_key] = Potential(
                parameters={"charge": partial_charge})

            matches[TopologyKey(atom_indices=(i, ))] = potential_key

        return matches, potentials
예제 #14
0
def _get_lj_parameters(openff_sys: "Interchange", 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
예제 #15
0
def _get_buck_parameters(openff_sys: "Interchange", 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
예제 #16
0
    def store_constraints(
        self,
        parameter_handlers: Any,
        topology: "OFFBioTop",
    ) -> None:

        if self.slot_map:
            self.slot_map = dict()

        constraint_handler = [
            p for p in parameter_handlers if type(p) == ConstraintHandler
        ][0]
        constraint_matches = constraint_handler.find_matches(topology)

        if any([type(p) == BondHandler for p in parameter_handlers]):
            bond_handler = [
                p for p in parameter_handlers if type(p) == BondHandler
            ][0]
            bonds = SMIRNOFFBondHandler._from_toolkit(
                parameter_handler=bond_handler,
                topology=topology,
            )
        else:
            bond_handler = None
            bonds = None

        for key, match in constraint_matches.items():
            topology_key = TopologyKey(atom_indices=key)
            smirks = match.parameter_type.smirks
            distance = match.parameter_type.distance
            if distance is not None:
                # This constraint parameter is fully specified
                potential_key = PotentialKey(id=smirks,
                                             associated_handler="Constraints")
                distance = match.parameter_type.distance
            else:
                # This constraint parameter depends on the BondHandler ...
                if bond_handler is None:
                    from openff.interchange.exceptions import MissingParametersError

                    raise MissingParametersError(
                        f"Constraint with SMIRKS pattern {smirks} found with no distance "
                        "specified, and no corresponding bond parameters were found. The distance "
                        "of this constraint is not specified.")
                # ... so use the same PotentialKey instance as the BondHandler to look up the distance
                potential_key = bonds.slot_map[topology_key]
                self.slot_map[topology_key] = potential_key
                distance = bonds.potentials[potential_key].parameters["length"]
            potential = Potential(parameters={
                "distance": distance,
            })
            self.constraints[
                potential_key] = potential  # type: ignore[assignment]
예제 #17
0
    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")
예제 #18
0
    def store_matches(
        self,
        force_field: "Forcefield",
        topology: "OFFBioTop",
    ) -> None:
        """Populate slotmap with key-val pairs of slots and unique potential Identifiers"""
        from foyer.atomtyper import find_atomtypes

        top_graph = TopologyGraph.from_openff_topology(
            openff_topology=topology)
        type_map = find_atomtypes(top_graph, forcefield=force_field)
        for key, val in type_map.items():
            top_key = TopologyKey(atom_indices=(key, ))
            self.slot_map[top_key] = PotentialKey(id=val["atomtype"])
예제 #19
0
    def __add__(self, other):
        """Combine two Interchange objects. This method is unstable and likely unsafe."""
        import mdtraj as md

        from openff.interchange.models import TopologyKey

        warnings.warn(
            "Iterchange object combination is experimental and likely to produce "
            "strange results. Use with caution!")

        self_copy = Interchange()
        self_copy._inner_data = deepcopy(self._inner_data)

        atom_offset = self_copy.topology.mdtop.n_atoms

        other_top = deepcopy(other.topology)

        for top_mol in other_top.topology_molecules:
            self_copy.topology.add_molecule(top_mol.reference_molecule)

        self_copy.topology.mdtop = md.Topology.from_openmm(
            self_copy.topology.to_openmm())

        for handler_name, handler in other.handlers.items():

            self_handler = self_copy.handlers[handler_name]

            for top_key, pot_key in handler.slot_map.items():

                new_atom_indices = tuple(idx + atom_offset
                                         for idx in top_key.atom_indices)
                new_top_key = TopologyKey(
                    atom_indices=new_atom_indices,
                    mult=top_key.mult,
                )

                self_handler.slot_map.update({new_top_key: pot_key})
                self_handler.potentials.update(
                    {pot_key: handler.potentials[pot_key]})

        new_positions = np.vstack([self_copy.positions, other.positions])
        self_copy.positions = new_positions

        if not np.all(self_copy.box == other.box):
            raise NotImplementedError(
                "Combination with unequal box vectors is not curretnly supported"
            )

        return self_copy
예제 #20
0
    def store_matches(
        self,
        atom_slots: Dict[TopologyKey, PotentialKey],
        topology: "OFFBioTop",
    ) -> None:
        for connection in getattr(topology, self.connection_attribute):
            try:
                atoms_iterable = connection.atoms
            except AttributeError:
                atoms_iterable = connection
            atoms_indices = tuple(atom.topology_atom_index
                                  for atom in atoms_iterable)
            top_key = TopologyKey(atom_indices=atoms_indices)

            pot_key_ids = tuple(
                _get_potential_key_id(atom_slots, idx)
                for idx in atoms_indices)

            self.slot_map[top_key] = PotentialKey(
                id=POTENTIAL_KEY_SEPARATOR.join(pot_key_ids))
예제 #21
0
def _convert_harmonic_angle_force(force):
    from openff.interchange.components.smirnoff import SMIRNOFFAngleHandler

    angle_handler = SMIRNOFFAngleHandler()

    n_parametrized_angles = force.getNumAngles()

    for idx in range(n_parametrized_angles):
        atom1, atom2, atom3, angle, k = force.getAngleParameters(idx)
        top_key = TopologyKey(atom_indices=(atom1, atom2, atom3))
        pot_key = PotentialKey(id=f"{atom1}-{atom2}-{atom3}")
        pot = Potential(parameters={
            "angle": from_simtk(angle),
            "k": from_simtk(k)
        })

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

    return angle_handler
예제 #22
0
def _convert_harmonic_bond_force(force):
    from openff.interchange.components.smirnoff import SMIRNOFFBondHandler

    bond_handler = SMIRNOFFBondHandler()

    n_parametrized_bonds = force.getNumBonds()

    for idx in range(n_parametrized_bonds):
        atom1, atom2, length, k = force.getBondParameters(idx)
        top_key = TopologyKey(atom_indices=(atom1, atom2))
        pot_key = PotentialKey(id=f"{atom1}-{atom2}")
        pot = Potential(parameters={
            "length": from_simtk(length),
            "k": from_simtk(k)
        })

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

    return bond_handler
예제 #23
0
def _convert_nonbonded_force(force):
    from openff.interchange.components.smirnoff import (
        SMIRNOFFElectrostaticsHandler,
        SMIRNOFFvdWHandler,
    )

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

    n_parametrized_particles = force.getNumParticles()

    for idx in range(n_parametrized_particles):
        charge, sigma, epsilon = force.getParticleParameters(idx)
        top_key = TopologyKey(atom_indices=(idx, ))
        pot_key = PotentialKey(id=f"{idx}")
        pot = Potential(parameters={
            "sigma": from_simtk(sigma),
            "epsilon": from_simtk(epsilon),
        })
        vdw_handler.slot_map.update({top_key: pot_key})
        vdw_handler.potentials.update({pot_key: pot})

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

    vdw_handler.cutoff = force.getCutoffDistance()
    electrostatics.cutoff = force.getCutoffDistance()

    if force.getNonbondedMethod() == openmm.NonbondedForce.PME:
        electrostatics.method = "pme"
    elif force.getNonbondedMethod() in {
            openmm.NonbondedForce.CutoffPeriodic,
            openmm.NonbondedForce.CutoffNonPeriodic,
    }:
        # TODO: Store reaction-field dielectric
        electrostatics.method = "reactionfield"
    elif force.getNonbondedMethod() == openmm.NonbondedForce.NoCutoff:
        raise Exception("NonbondedMethod NoCutoff is not supported")

    return vdw_handler, electrostatics
예제 #24
0
    def charges(self) -> Dict[TopologyKey, unit.Quantity]:
        """Returns the total partial charge on each particle in the associated interchange."""

        charges = defaultdict(lambda: 0.0 * unit.e)

        for topology_key, potential_key in self.slot_map.items():

            potential = self.potentials[potential_key]

            for parameter_key, parameter_value in potential.parameters.items():

                if parameter_key != "charge" and parameter_key != "charge_increment":
                    raise NotImplementedError()

                charge = parameter_value
                charges[topology_key.atom_indices[0]] += charge

        return {
            TopologyKey(atom_indices=(index, )): charge
            for index, charge in charges.items()
        }
예제 #25
0
    def store_matches(
        self,
        parameter_handler: ParameterHandler,
        topology: Union["Topology", "OFFBioTop"],
    ) -> None:
        """
        Populate self.slot_map with key-val pairs of slots
        and unique potential identifiers

        """
        parameter_handler_name = getattr(parameter_handler, "_TAGNAME", None)
        if self.slot_map:
            # TODO: Should the slot_map always be reset, or should we be able to partially
            # update it? Also Note the duplicated code in the child classes
            self.slot_map = dict()
        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,
                associated_handler=parameter_handler_name)
            self.slot_map[topology_key] = potential_key
예제 #26
0
    def _library_charge_to_potentials(
        cls,
        atom_indices: Tuple[int, ...],
        parameter: LibraryChargeHandler.LibraryChargeType,
    ) -> Tuple[Dict[TopologyKey, PotentialKey], Dict[PotentialKey, Potential]]:
        """Maps a matched library charge parameter to a set of potentials."""

        matches = {}
        potentials = {}

        for i, (atom_index,
                charge) in enumerate(zip(atom_indices, parameter.charge)):
            topology_key = TopologyKey(atom_indices=(atom_index, ))
            potential_key = PotentialKey(id=parameter.smirks,
                                         mult=i,
                                         associated_handler="LibraryCharges")
            potential = Potential(parameters={"charge": from_simtk(charge)})

            matches[topology_key] = potential_key
            potentials[potential_key] = potential

        return matches, potentials
예제 #27
0
    def store_matches(self, parameter_handler: "ImproperTorsionHandler",
                      topology: "OFFBioTop") -> None:
        """
        Populate self.slot_map with key-val pairs of slots
        and unique potential identifiers

        """
        if self.slot_map:
            self.slot_map = dict()
        matches = parameter_handler.find_matches(topology)
        for key, val in matches.items():
            parameter_handler._assert_correct_connectivity(
                val,
                [
                    (0, 1),
                    (1, 2),
                    (1, 3),
                ],
            )
            n_terms = len(val.parameter_type.k)
            for n in range(n_terms):

                smirks = val.parameter_type.smirks
                non_central_indices = [key[0], key[2], key[3]]

                for permuted_key in [(
                        non_central_indices[i],
                        non_central_indices[j],
                        non_central_indices[k],
                ) for (i, j, k) in [(0, 1, 2), (1, 2, 0), (2, 0, 1)]]:

                    topology_key = TopologyKey(atom_indices=(key[1],
                                                             *permuted_key),
                                               mult=n)
                    potential_key = PotentialKey(
                        id=smirks,
                        mult=n,
                        associated_handler="ImproperTorsions")
                    self.slot_map[topology_key] = potential_key
예제 #28
0
    def ethanol_with_rb_torsions(self):
        mol = Molecule.from_smiles("CC")
        mol.generate_conformers(n_conformers=1)
        top = mol.to_topology()
        parsley = ForceField("openff-1.0.0.offxml")
        out = Interchange.from_smirnoff(parsley, 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")

        return out
예제 #29
0
    def _find_reference_matches(
        cls,
        parameter_handlers: Dict[str, "ElectrostaticsHandlerType"],
        reference_molecule: Molecule,
    ) -> Tuple[Dict[TopologyKey, PotentialKey], Dict[PotentialKey, Potential]]:
        """Constructs a slot and potential map for a particular reference molecule
        and set of parameter handlers."""

        matches = {}
        potentials = {}

        expected_matches = {i for i in range(reference_molecule.n_atoms)}

        for handler_type in cls.charge_precedence():

            if handler_type not in parameter_handlers:
                continue

            parameter_handler = parameter_handlers[handler_type]

            slot_matches, slot_potentials = None, {}
            am1_matches, am1_potentials = None, {}

            if handler_type in ["LibraryCharges", "ChargeIncrementModel"]:

                slot_matches, slot_potentials = cls._find_slot_matches(
                    parameter_handler, reference_molecule)

            if handler_type in ["ToolkitAM1BCC", "ChargeIncrementModel"]:

                am1_matches, am1_potentials = cls._find_am1_matches(
                    parameter_handler, reference_molecule)

            if slot_matches is None and am1_matches is None:
                raise NotImplementedError()

            elif slot_matches is not None and am1_matches is not None:

                am1_matches = {
                    TopologyKey(atom_indices=topology_key.atom_indices,
                                mult=0): potential_key
                    for topology_key, potential_key in am1_matches.items()
                }
                slot_matches = {
                    TopologyKey(atom_indices=topology_key.atom_indices,
                                mult=1): potential_key
                    for topology_key, potential_key in slot_matches.items()
                }

                matched_atom_indices = {
                    index
                    for key in slot_matches for index in key.atom_indices
                }
                matched_atom_indices.intersection_update({
                    index
                    for key in am1_matches for index in key.atom_indices
                })

            elif slot_matches is not None:
                matched_atom_indices = {
                    index
                    for key in slot_matches for index in key.atom_indices
                }
            else:
                matched_atom_indices = {
                    index
                    for key in am1_matches for index in key.atom_indices
                }

            if matched_atom_indices != expected_matches:
                # Handle the case where a handler could not fully assign the charges
                # to the whole molecule.
                continue

            matches.update(slot_matches if slot_matches is not None else {})
            matches.update(am1_matches if am1_matches is not None else {})

            potentials.update(slot_potentials)
            potentials.update(am1_potentials)

            break

        found_matches = {
            index
            for key in matches for index in key.atom_indices
        }

        if found_matches != expected_matches:

            raise RuntimeError(
                f"{reference_molecule.to_smiles(explicit_hydrogens=False)} could "
                f"not be fully assigned charges.")

        return matches, potentials
예제 #30
0
def _get_potential_key_id(atom_slots: Dict[TopologyKey, PotentialKey], idx):
    """From a dictionary of TopologyKey: PotentialKey, get the PotentialKey id"""
    top_key = TopologyKey(atom_indices=(idx, ))
    return atom_slots[top_key].id