def _get_ligand(mol: Molecule) -> Molecule: """Extract a single ligand from **mol** as a copy.""" at_list = [] res = mol.atoms[-1].properties.pdb_info.ResidueNumber for at in reversed(mol.atoms): if at.properties.pdb_info.ResidueNumber == res: at_list.append(at) else: ret = Molecule() ret.atoms = at_list ret.bonds = list( set(chain.from_iterable(at.bonds for at in at_list))) return ret.copy()
def get_asa_fragments(qd: Molecule) -> Tuple[List[Molecule], Molecule]: """Construct the fragments for an activation strain analyses. Parameters ---------- qd : |plams.Molecule| A Molecule whose atoms' properties should be marked with `pdb_info.ResidueName`. Atoms in the core should herein be marked with ``"COR"``. Returns ------- :class:`list` [|plams.Molecule|] and |plams.Molecule| A list of ligands and the core. Fragments are defined based on connectivity patterns (or lack thereof). """ # Delete all atoms within the core mol_complete = qd.copy() core = Molecule() core.properties = mol_complete.properties.copy() core_atoms = [at for at in mol_complete if at.properties.pdb_info.ResidueName == 'COR'] for atom in core_atoms: mol_complete.delete_atom(atom) atom.mol = core core.atoms = core_atoms mol_complete.properties.name += '_frags' core.properties.name += '_core' # Fragment the molecule ligand_list = mol_complete.separate() # Set atomic properties for at1, at2 in zip(chain(*ligand_list), mol_complete): at1.properties.symbol = at2.properties.symbol at1.properties.charge_float = at2.properties.charge_float for at1, at2 in zip(core, qd): at1.properties.symbol = at2.properties.symbol at1.properties.charge_float = at2.properties.charge_float # Set the prm parameter which points to the created .prm file name = mol_complete.properties.name[:-1] path = mol_complete.properties.path prm = mol_complete.properties.prm for mol in ligand_list: mol.properties.name = name mol.properties.path = path mol.properties.prm = prm return ligand_list, core
def set_qd(qd: Molecule, mol_dict: Settings) -> Molecule: """Update quantum dots imported by :func:`.read_mol`.""" # Create ligand (and anchor) molecules ligand = molkit.from_smiles(mol_dict.ligand_smiles) ligand_rdmol = molkit.to_rdmol(ligand) anchor = molkit.from_smiles(mol_dict.ligand_anchor) anchor_rdmol = molkit.to_rdmol(anchor) qd_rdmol = molkit.to_rdmol(qd) # Create arrays of atomic indices of the core and ligands lig_idx = 1 + np.array(qd_rdmol.GetSubstructMatches(ligand_rdmol)) core_idx = np.arange(1, len(qd))[~lig_idx] lig_idx = lig_idx.ravel().tolist() core_idx = core_idx.tolist() # Guess bonds if mol_dict.guess_bonds: qd.guess_bonds(atom_subset=[qd[i] for i in lig_idx]) # Reorder all atoms: core atoms first followed by ligands qd.atoms = [qd[i] for i in core_idx] + [qd[j] for i in lig_idx for j in i] # Construct a list with the indices of all ligand anchor atoms core_idx_max = 1 + len(core_idx) _anchor_idx = ligand_rdmol.GetSubstructMatch(anchor_rdmol)[0] start = core_idx_max + _anchor_idx stop = core_idx_max + _anchor_idx + np.product(lig_idx.shape) step = len(ligand) anchor_idx = list(range(start, stop, step)) # Update the properties of **qd** for i in anchor_idx: qd[i].properties.anchor = True qd.properties.indices = list(range(1, core_idx_max)) + anchor_idx qd.properties.job_path = [] qd.properties.name = mol_dict.name qd.properties.path = mol_dict.path qd.properties.ligand_smiles = Chem.CanonSmiles(mol_dict.ligand_smiles) qd.properties.ligand_anchor = f'{ligand[_anchor_idx].symbol}{_anchor_idx}' # Update the pdb_info of all atoms for i, at in enumerate(qd, 1): at.properties.pdb_info.SerialNumber = i if i <= core_idx_max: # A core atom at.properties.pdb_info.ResidueNumber = 1 else: # A ligand atom at.properties.pdb_info.ResidueNumber = 2 + int( (i - core_idx_max) / len(ligand))
def _molecule_from_rdmol( rdmol: Chem.Mol, smiles: str, matches: Iterable[Sequence[int]], split: bool = True, ) -> Generator[Molecule, None, None]: """Construct a PLAMS molecule from the passed rdkit mol's ``MolBlock``.""" for tup in matches: try: i, *_, j = tup # type: int, Any, None | int except ValueError: i = tup[0] j = None # Split the capping atom (j) from the main molecule if j is not None and split: if i > j: i -= 1 rdmol_edit = Chem.EditableMol(rdmol) rdmol_edit.RemoveAtom(j) rdmol_new = rdmol_edit.GetMol() anchor = rdmol_new.GetAtoms()[i] anchor.SetFormalCharge(anchor.GetFormalCharge() - 1) else: rdmol_new = rdmol # Parse the .mol block and convert it into a PLAMS molecule mol_block = Chem.MolToMolBlock(rdmol) iterator = _iter_mol_block(mol_block, size=len(rdmol.GetAtoms())) mol = Molecule() mol.atoms = [Atom(symbol=symbol, coords=xyz, mol=mol) for symbol, xyz in iterator] for bond in rdmol.GetBonds(): at1 = mol.atoms[bond.GetBeginAtomIdx()] at2 = mol.atoms[bond.GetEndAtomIdx()] mol.add_bond(Bond(at1, at2, order=bond.GetBondTypeAsDouble())) # Set properties and yield mol.properties.smiles = smiles mol.properties.dummies = mol.atoms[i] mol.properties.anchor = f"{mol.properties.dummies.symbol}{i + 1}" yield mol
def canonicalize_mol(mol: Molecule, inplace: bool = True) -> Optional[Molecule]: """Take a PLAMS molecule and sort its atoms based on their canonical rank. .. _rdkit.Chem.CanonicalRankAtoms: https://www.rdkit.org/docs/source/rdkit.Chem.rdmolfiles.html#rdkit.Chem.rdmolfiles.CanonicalRankAtoms Examples -------- .. code:: python >>> from scm.plams import Molecule, from_smiles # Methane >>> mol: Molecule = from_smiles('C') >>> print(mol) # doctest: +SKIP Atoms: 1 H 0.640510 0.640510 -0.640510 2 H 0.640510 -0.640510 0.640510 3 C 0.000000 0.000000 0.000000 4 H -0.640510 0.640510 0.640510 5 H -0.640510 -0.640510 -0.640510 >>> canonicalize_mol(mol) >>> print(mol) # doctest: +SKIP Atoms: 1 C 0.000000 0.000000 0.000000 2 H -0.640510 -0.640510 -0.640510 3 H -0.640510 0.640510 0.640510 4 H 0.640510 -0.640510 0.640510 5 H 0.640510 0.640510 -0.640510 Parameters ---------- mol : |plams.Molecule|_ A PLAMS molecule. inplace : bool If ``True``, perform an inplace update of **mol** rather than returning a new :class:`Molecule` instance. Returns ------- |plams.Molecule|_ Optional: if ``inplace=False``, return a copy of **mol** with its atoms sorted by their canonical rank. See Also -------- * rdkit.Chem.CanonicalRankAtoms_: Returns the canonical atom ranking for each atom of a molecule fragment. """ # noqa rdmol = molkit.to_rdmol(mol) idx_collection = Chem.CanonicalRankAtoms(rdmol) # Reverse sort Molecule.atoms by the atomic indices in idx_collection if inplace: mol.atoms = [ at for _, at in sorted(zip(idx_collection, mol.atoms), reverse=True) ] return else: ret = mol.copy() ret.atoms = [ at for _, at in sorted(zip(idx_collection, ret.atoms), reverse=True) ] return ret