Exemple #1
0
    def repr_Molecule(self, obj: Molecule, level: int) -> str:  # noqa: N802
        """Create a :class:`str` representation of a |plams.Molecule| instance."""
        if level <= 0:
            return f'{obj.__class__.__name__}(...)'
        elif not obj:
            return f'{obj.__class__.__name__}()'

        obj.set_atoms_id()
        ret = 'Atoms: \n'
        i = self.maxMolecule

        # Print atoms
        kwargs = {'decimal': self.maxfloat, 'space': 14 - (6 - self.maxfloat)}
        for atom in obj.atoms[:i]:
            ret += f'  {atom.id:<5d}{atom.str(**kwargs).strip()}\n'
        if len(obj.atoms) > i:
            ret += '  ...\n'

        # Print bonds
        if len(obj.bonds):
            ret += 'Bonds: \n'
            for bond in obj.bonds[:i]:
                ret += f'  ({bond.atom1.id})--{bond.order:1.1f}--({bond.atom2.id})\n'
            if len(obj.bonds) > i:
                ret += '  ...\n'

        # Print lattice vectors
        if obj.lattice:
            ret += 'Lattice:\n'
            for vec in obj.lattice:
                ret += '  {:16.10f} {:16.10f} {:16.10f}\n'.format(*vec)

        obj.unset_atoms_id()
        indent = 4 * ' '
        return f'{obj.__class__.__name__}(\n{textwrap.indent(ret[:-1], indent)}\n)'
Exemple #2
0
def get_bonds(mol: Molecule) -> np.ndarray:
    """Return an array with the atomic indices defining all bonds in **mol**.

    Parameters
    ----------
    mol : |plams.Molecule|_
        A PLAMS molecule.

    Returns
    -------
    :math:`n*2` |np.ndarray|_ [|np.int64|_]:
        A 2D array with atomic indices defining :math:`n` bonds.

    """
    mol.set_atoms_id()
    bonds = [(b.atom1.id, b.atom2.id) for b in mol.bonds]

    ret = np.array(bonds, dtype=int, ndmin=2)
    mol.unset_atoms_id()
    if not bonds:  # If no angles are found
        return ret

    # Sort horizontally
    mass = np.array([[mol[j].mass for j in i] for i in ret])
    idx1 = np.argsort(mass, axis=1)[:, ::-1]
    ret[:] = np.take_along_axis(ret, idx1, axis=1)

    # Sort and return vertically
    idx2 = np.argsort(ret, axis=0)[:, 0]
    return ret[idx2]
Exemple #3
0
    def __init__(self, mol: Molecule) -> None:
        """Initialize the instance."""
        mol.set_atoms_id(start=0)

        self._mol = mol
        self._dist_mat = _get_dist_mat(mol)
        self._dist_mat.setflags(write=False)
        self.id_set = set()
        self.neighbor_dict = {}
Exemple #4
0
def _bond_id_generator(
        mol: Molecule) -> Generator[Tuple[int, int, int, int], None, None]:
    """Yield all permutations of the bond-defining atoms indices in **mol**."""
    mol.set_atoms_id(start=0)
    for b in mol.bonds:
        id1 = b.atom1.id
        id2 = b.atom2.id
        yield id1, id2, id2, id1

    mol.unset_atoms_id()
Exemple #5
0
def get_dihedrals(mol: Molecule) -> np.ndarray:
    """Return an array with the atomic indices defining all proper dihedral angles in **mol**.

    Parameters
    ----------
    mol : |plams.Molecule|_
        A PLAMS molecule.

    Returns
    -------
    :math:`n*4` |np.ndarray|_ [|np.int64|_]:
        A 2D array with atomic indices defining :math:`n` proper dihedrals.

    """
    mol.set_atoms_id()
    dihed: List[Tuple[int, int, int, int]] = []
    dihed_append = dihed.append

    for b1 in mol.bonds:
        if not (len(b1.atom1.bonds) > 1 and len(b1.atom2.bonds) > 1):
            continue

        at2, at3 = b1
        for b2 in at2.bonds:
            at1 = b2.other_end(at2)
            if at1 == at3:
                continue

            for b3 in at3.bonds:
                at4 = b3.other_end(at3)
                if at4 != at2:
                    dihed_append((at1.id, at2.id, at3.id, at4.id))

    ret = np.array(dihed, dtype=int, ndmin=2)
    mol.unset_atoms_id()
    if not dihed:  # If no dihedrals are found
        return ret

    # Sort horizontally
    mass = np.array([[mol[j].mass for j in i] for i in ret[:, 1:3]])
    idx1 = np.argsort(mass, axis=1)
    ret[:, ::3] = np.take_along_axis(ret[:, ::3], idx1, axis=1)
    ret[:, 1:3] = np.take_along_axis(ret[:, 1:3], idx1, axis=1)

    # Sort and return vertically
    idx2 = np.argsort(ret, axis=0)[:, 0]
    return ret[idx2]
Exemple #6
0
def adf_connectivity(mol: Molecule) -> List[str]:
    """Create an AMS-compatible connectivity list.

    Parameters
    ----------
    mol : |plams.Molecule|_
        A PLAMS molecule with :math:`n` bonds.

    Returns
    -------
    :math:`n` |list|_ [|str|_]
        An ADF-compatible connectivity list of :math:`n` bonds.

    """
    mol.set_atoms_id()

    # Create list of indices of all aromatic bonds
    try:
        rdmol = molkit.to_rdmol(mol)
    except Exception as ex:
        if type(ex) is ValueError or ex.__class__.__name__ == 'ArgumentError':
            # Plan B: ignore aromatic bonds
            bonds = [
                f'{bond.atom1.id} {bond.atom2.id} {bond.order:.1f}'
                for bond in mol.bonds
            ]
            mol.unset_atoms_id()
            return bonds
        raise ex

    aromatic = [bond.GetIsAromatic() for bond in rdmol.GetBonds()]

    # Create a list of bond orders; aromatic bonds get a bond order of 1.5
    bond_orders = [(1.5 if ar else bond.order)
                   for ar, bond in zip(aromatic, mol.bonds)]
    bonds = [
        f'{bond.atom1.id} {bond.atom2.id} {order:.1f}'
        for bond, order in zip(mol.bonds, bond_orders)
    ]
    mol.unset_atoms_id()

    return bonds
Exemple #7
0
def _find_idx(mol: Molecule, bond: Bond) -> List[int]:
    """Return the atomic indices of all atoms on the side of **bond.atom2**."""
    ret = []
    mol.set_atoms_id(start=0)
    for at in mol:
        at._visited = False

    def dfs(at1, mol):
        at1._visited = True
        ret.append(at1.id)
        for bond in at1.bonds:
            at2 = bond.other_end(at1)
            if not at2._visited:
                dfs(at2, mol)

    bond.atom1._visited = bond.atom2._visited = True
    dfs(bond.atom2, mol)

    mol.unset_atoms_id()
    return ret
Exemple #8
0
def get_impropers(mol: Molecule) -> np.ndarray:
    """Return an array with the atomic indices defining all improper dihedral angles in **mol**.

    Parameters
    ----------
    mol : |plams.Molecule|_
        A PLAMS molecule.

    Returns
    -------
    :math:`n*4` |np.ndarray|_ [|np.int64|_]:
        A 2D array with atomic indices defining :math:`n` improper dihedrals.

    """
    mol.set_atoms_id()
    impropers: List[Tuple[int, int, int, int]] = []
    impropers_append = impropers.append

    for at1 in mol.atoms:
        order = [bond.order for bond in at1.bonds]
        if len(order) != 3:
            continue

        if 2.0 in order or 1.5 in order:
            at2, at3, at4 = [bond.other_end(at1) for bond in at1.bonds]
            impropers_append((at1.id, at2.id, at3.id, at4.id))

    ret = np.array(impropers, dtype=int, ndmin=2)
    mol.unset_atoms_id()
    if not impropers:  # If no impropers are found
        return ret

    # Sort along the rows of columns 2, 3 & 4 based on atomic mass in descending order
    mass = np.array([[mol[j].mass for j in i] for i in ret[:, 1:]])
    idx1 = np.argsort(mass, axis=1)
    idx1[:, 1:] = idx1[:, 1:][::-1]
    ret[:, 1:] = np.take_along_axis(ret[:, 1:], idx1, axis=1)

    # Sort vertically
    idx2 = np.argsort(ret, axis=0)[:, 0]
    return ret[idx2]
Exemple #9
0
def get_angles(mol: Molecule) -> np.ndarray:
    """Return an array with the atomic indices defining all angles in **mol**.

    Parameters
    ----------
    mol : |plams.Molecule|_
        A PLAMS molecule.

    Returns
    -------
    :math:`n*3` |np.ndarray|_ [|np.int64|_]:
        A 2D array with atomic indices defining :math:`n` angles.

    """
    mol.set_atoms_id()
    angle: List[Tuple[int, int, int]] = []
    angle_append = angle.append

    for at2 in mol.atoms:
        if len(at2.bonds) < 2:
            continue

        at_other = [bond.other_end(at2) for bond in at2.bonds]
        for i, at1 in enumerate(at_other, 1):
            for at3 in at_other[i:]:
                angle_append((at1.id, at2.id, at3.id))

    ret = np.array(angle, dtype=int, ndmin=2)
    mol.unset_atoms_id()
    if not angle:  # If no angles are found
        return ret

    # Sort horizontally
    mass = np.array([[mol[j].mass for j in i] for i in ret[:, 0::2]])
    idx1 = np.argsort(mass, axis=1)[:, ::-1]
    ret[:, ::2] = np.take_along_axis(ret[:, ::2], idx1, axis=1)

    # Sort and return vertically
    idx2 = np.argsort(ret, axis=0)[:, 0]
    return ret[idx2]