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]
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)'
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()
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]
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
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
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]
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]