Exemplo n.º 1
0
def openff_molecule_from_networkx(nx_graph: networkx.Graph) -> Molecule:
    """Attempts to create an OpenFF molecule from a networkx graph representation.

    Notes:
        This method will strip all stereochemistry and aromaticity information.

    Args:
        nx_graph: The graph representation.

    Returns:
        The OpenFF molecule object.
    """

    molecule = Molecule()

    for node_index in nx_graph.nodes:
        node = nx_graph.nodes[node_index]

        molecule.add_atom(
            Element.getBySymbol(node["element"]).atomic_number,
            node["formal_charge"],
            False,
        )

    for atom_index_a, atom_index_b in nx_graph.edges:

        molecule.add_bond(
            atom_index_a,
            atom_index_b,
            nx_graph[atom_index_a][atom_index_b]["bond_order"],
            False,
        )

    return molecule
Exemplo n.º 2
0
def split_openff_molecule(molecule: Molecule) -> List[Molecule]:
    """
    For a gievn openff molecule split it into its component parts if it is actually a multi-component system.

    Args:
        molecule:
            The openff.toolkit.topology.Molecule which should be split.
    """
    sub_graphs = list(nx.connected_components(molecule.to_networkx()))
    if len(sub_graphs) == 1:
        return [
            molecule,
        ]
    component_molecules = []
    for sub_graph in sub_graphs:
        # map the old index to the new one
        index_mapping = {}
        comp_mol = Molecule()
        for atom in sub_graph:
            new_index = comp_mol.add_atom(**molecule.atoms[atom].to_dict())
            index_mapping[atom] = new_index
        for bond in molecule.bonds:
            if bond.atom1_index in sub_graph and bond.atom2_index in sub_graph:
                bond_data = {
                    "atom1": comp_mol.atoms[index_mapping[bond.atom1_index]],
                    "atom2": comp_mol.atoms[index_mapping[bond.atom2_index]],
                    "bond_order": bond.bond_order,
                    "stereochemistry": bond.stereochemistry,
                    "is_aromatic": bond.is_aromatic,
                    "fractional_bond_order": bond.fractional_bond_order,
                }
                comp_mol.add_bond(**bond_data)
        # move the conformers
        if molecule.n_conformers != 0:
            for conformer in molecule.conformers:
                new_conformer = np.zeros((comp_mol.n_atoms, 3))
                for i in sub_graph:
                    new_conformer[index_mapping[i]] = conformer[i]
                comp_mol.add_conformer(new_conformer * unit.angstrom)
        component_molecules.append(comp_mol)
    return component_molecules
Exemplo n.º 3
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
Exemplo n.º 4
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