Esempio n. 1
0
 def _prevent_weird_rings(self, mol: Chem.Mol):
     if not isinstance(mol, Chem.RWMol):
         mol = Chem.RWMol(mol)
     ringatoms = self._get_ring_info(mol) #GetRingInfo().AtomRings()
     for ring_A, ring_B in itertools.combinations(ringatoms, r=2):
         shared = set(ring_A).intersection(set(ring_B))
         if len(shared) == 0:
             log.debug('This molecule has some separate rings')
             pass  # separate rings
         elif len(shared) == 1:
             log.debug('This molecule has a spiro bicycle')
             pass  # spiro ring.
         elif len(shared) == 2:
             log.debug('This molecule has a fused ring')
             if mol.GetBondBetweenAtoms(*shared) is not None:
                 pass  # indole/naphtalene
                 small, big = sorted([ring_A, ring_B], key=lambda ring: len(ring))
                 if len(small) == 4:
                     log.warning('This molecule has a benzo-azetine–kind-of-thing: expanding to indole')
                     # Chem.MolFromSmiles('C12CCCCC1CC2')
                     # benzo-azetine is likely an error: add and extra atom
                     a, b = set(small).difference(big)
                     self._place_between(mol, a, b)
                 elif len(small) == 3:
                     log.warning('This molecule has a benzo-cyclopropane–kind-of-thing: expanding to indole')
                     # Chem.MolFromSmiles('C12CCCCC1C2')
                     # benzo-cyclopronane is actually impossible at this stage.
                     a = list(set(small).difference(big))[0]
                     for b in shared:
                         self._place_between(mol, a, b)
                 else:
                     pass  # indole and nathalene
             elif (len(ring_A), len(ring_B)) == (6, 6):
                 raise Exception('This is utterly impossible')
             else:
                 print(f'mysterious ring system {len(ring_A)} + {len(ring_B)}')
                 pass  # ????
         elif len(shared) < self.atoms_in_bridge_cutoff:
             #adamantene/norbornane/tropinone kind of thing
             log.warning('This molecule has a bridge: leaving')
             pass  # ideally check if planar...
         else:
             log.warning('This molecule has a bridge that will be removed')
             mol = self._prevent_bridge_ring(mol, ring_A)
             # start from scratch.
             return self._prevent_weird_rings(mol)
     return mol.GetMol()
Esempio n. 2
0
    def expand_ring(self, mol: Chem.Mol, bonded_as_original=False):
        """
        Undoes collapse ring

        :param mol:
        :param bonded_as_original: if false bonds by proximity, if true bonds as remembered
        :return:
        """
        mol = Chem.RWMol(mol)
        rings = self._get_expansion_data(mol)
        self._place_ring_atoms(mol, rings)
        mergituri = self._ring_overlap_scenario(mol, rings)
        # bonding
        log.debug('Starting ring expansion')
        if bonded_as_original:
            self._restore_original_bonding(mol, rings)
            self._fix_overlap(mol, mergituri)
            self._delete_collapsed(mol)
        else:
            self._connenct_ring(mol, rings)
            self._mark_neighbors(mol, rings)
            self._fix_overlap(mol, mergituri)
            log.debug('Deleting collapsed')
            self._delete_collapsed(mol)
            log.debug('Infering bonding')
            self._infer_bonding_by_proximity(mol) # absorb_overclose and join_overclose
        # this should not happen... but it can!)
        mol = self._emergency_joining(mol)
        # _emergency_joining returns a Chem.Mol not a Chem.RWMol
        # prevent weird nested rings.
        mol = self._prevent_conjoined_ring(mol)
        mol = self._prevent_weird_rings(mol)
        mol = self._prevent_allene(mol)
        if mol is None:
            raise ValueError('(Impossible) Failed at some point...')
        elif isinstance(mol, Chem.RWMol):
            return mol.GetMol()
        else:
            return mol
Esempio n. 3
0
 def _prevent_conjoined_ring(self, mol: Chem.Mol) -> Chem.Mol:
     """
     This kills bridging bonds with not atoms in the bridge within rings.
     So it is bridged, fused and spiro safe.
     It removes only one bond, so andamantane/norbornane are safe.
     :param mol:
     :return:
     """
     c = Counter([i for ring in self._get_ring_info(mol) for i in ring])
     nested = [k for k in c if c[k] >= 3]
     pairs = [(idx_a, idx_b) for idx_a, idx_b in itertools.combinations(nested, r=2) if
              mol.GetBondBetweenAtoms(idx_a, idx_b) is not None]
     rank = sorted(pairs, key=lambda x: c[x[0]] + c[x[1]], reverse=True)
     if len(rank) > 0:
         idx_a, idx_b = rank[0]
         if not isinstance(mol, Chem.RWMol):
             mol = Chem.RWMol(mol)
         mol.RemoveBond(idx_a, idx_b) # SetBoolProp('_IsRingBond') is not important
         log.info(f'Zero-atom bridged ring issue: bond between {idx_a}-{idx_b} removed')
         return self._prevent_conjoined_ring(mol)
     elif isinstance(mol, Chem.RWMol):
         return mol.GetMol()
     else:
         return mol
Esempio n. 4
0
    def collapse_ring(self, mol: Chem.Mol) -> Chem.Mol:
        """
        Collapses a ring(s) into a single dummy atom(s).
        Stores data as JSON in the atom.

        :param mol:
        :return:
        """
        self.store_positions(mol)
        mol = Chem.RWMol(mol)
        conf = mol.GetConformer()
        center_idxs = []
        morituri = []
        old2center = defaultdict(list)
        for atomset in mol.GetRingInfo().AtomRings():
            morituri.extend(atomset)
            neighs = []
            neighbonds = []
            bonds = []
            xs = []
            ys = []
            zs = []
            elements = []
            # add elemental ring
            c = mol.AddAtom(Chem.Atom('C'))
            center_idxs.append(c)
            central = mol.GetAtomWithIdx(c)
            name = mol.GetProp('_Name') if mol.HasProp('_Name') else '???'
            central.SetProp('_ori_name', name),
            # get data for storage
            for i in atomset:
                old2center[i].append(c)
                atom = mol.GetAtomWithIdx(i)
                neigh_i = [a.GetIdx() for a in atom.GetNeighbors()]
                neighs.append(neigh_i)
                bond = [mol.GetBondBetweenAtoms(i, j).GetBondType().name for j in neigh_i]
                bonds.append(bond)
                pos = conf.GetAtomPosition(i)
                xs.append(pos.x)
                ys.append(pos.y)
                zs.append(pos.z)
                elements.append(atom.GetSymbol())
            # store data in elemental ring
            central.SetIntProp('_ori_i', -1)
            central.SetProp('_ori_is', json.dumps(atomset))
            central.SetProp('_neighbors', json.dumps(neighs))
            central.SetProp('_xs', json.dumps(xs))
            central.SetProp('_ys', json.dumps(ys))
            central.SetProp('_zs', json.dumps(zs))
            central.SetProp('_elements', json.dumps(elements))
            central.SetProp('_bonds', json.dumps(bonds))
            conf.SetAtomPosition(c, Point3D(*[sum(axis) / len(axis) for axis in (xs, ys, zs)]))
        for atomset, center_i in zip(mol.GetRingInfo().AtomRings(), center_idxs):
            # bond to elemental ring
            central = mol.GetAtomWithIdx(center_i)
            neighss = json.loads(central.GetProp('_neighbors'))
            bondss = json.loads(central.GetProp('_bonds'))
            for neighs, bonds in zip(neighss, bondss):
                for neigh, bond in zip(neighs, bonds):
                    if neigh not in atomset:
                        bt = getattr(Chem.BondType, bond)
                        if neigh not in morituri:
                            mol.AddBond(center_i, neigh, bt)
                        else:
                            for other_center_i in old2center[neigh]:
                                if center_i != other_center_i:
                                    if not mol.GetBondBetweenAtoms(center_i, other_center_i):
                                        mol.AddBond(center_i, other_center_i, bt)
                                    break
                            else:
                                raise ValueError(f'Cannot find what {neigh} became')
        for i in sorted(set(morituri), reverse=True):
            mol.RemoveAtom(self._get_new_index(mol, i))
        return mol.GetMol()