def _get_square(self, first: Chem.Atom, second: Chem.Atom) -> Union[Tuple[int, int], None]: for third in [neigh for neigh in second.GetNeighbors() if neigh.GetIdx() != first.GetIdx()]: fourths = self._get_triangles(first, third) if fourths and len(fourths) > 1: fourth = [f for f in fourths if f != second.GetIdx()][0] return third.GetIdx(), fourth else: return None
def get_other(bond: Chem.Bond, atom: Chem.Atom) -> Chem.Atom: """Given an bond and an atom return the other.""" if bond.GetEndAtomIdx() == atom.GetIdx( ): # atom == itself gives false. return bond.GetBeginAtom() else: return bond.GetEndAtom()
def from_rdkit(cls, rd_atom: Chem.Atom) -> "Atom": """ Build a QUBEKit atom from an rdkit atom instance. """ atomic_number = rd_atom.GetAtomicNum() index = rd_atom.GetIdx() formal_charge = rd_atom.GetFormalCharge() aromatic = rd_atom.GetIsAromatic() bonds = [a.GetIdx() for a in rd_atom.GetNeighbors()] # check for names in the normal places pdb, mol2 and mol if rd_atom.HasProp("_Name"): name = rd_atom.GetProp("_Name") elif rd_atom.HasProp("_TriposAtomName"): name = rd_atom.GetProp("_TriposAtomName") else: try: name = rd_atom.GetMonomerInfo().GetName().strip() except AttributeError: name = None # stereochem if rd_atom.HasProp("_CIPCode"): stereo_code = rd_atom.GetProp("_CIPCode") else: stereo_code = None return cls( atomic_number=atomic_number, atom_index=index, atom_name=name, formal_charge=formal_charge, aromatic=aromatic, stereochemistry=stereo_code, bonds=bonds, )
def _get_triangle(self, first: Chem.Atom, second: Chem.Atom) -> Union[int, None]: """ Get the third atom... :param first: atom :param second: atom :return: atom index of third """ get_neigh_idxs = lambda atom: [neigh.GetIdx() for neigh in atom.GetNeighbors()] f_neighs = get_neigh_idxs(first) s_neighs = get_neigh_idxs(second) a = set(f_neighs) - {first.GetIdx(), second.GetIdx()} b = set(s_neighs) - {first.GetIdx(), second.GetIdx()} others = list(a.intersection(b)) if len(others) == 0: # is disjoined return None else: return others[0]
def _get_measurements(self, conf: Chem.Conformer, a: Chem.Atom, b: Chem.Atom, c: Chem.Atom, d: Chem.Atom): ai = a.GetIdx() bi = b.GetIdx() ci = c.GetIdx() di = d.GetIdx() dist = 0 angle = 0 tor = 0 try: dist = Chem.rdMolTransforms.GetBondLength(conf, ai, bi) angle = 180 - Chem.rdMolTransforms.GetAngleDeg(conf, ai, bi, ci) tor = Chem.rdMolTransforms.GetDihedralDeg(conf, ai, bi, ci, di) except ValueError: pass if str(tor) == 'nan': #quicker than isnan. tor = 0 return self._Measure(distance=dist, angle=angle, torsion=tor)
def _get_triangles(self, first: Chem.Atom, second: Chem.Atom) -> Union[List[int], None]: """ Get the third atoms... (Square situation) :param first: atom :param second: atom :return: atom index of third """ get_neigh_idxs = lambda atom: [neigh.GetIdx() for neigh in atom.GetNeighbors() if self._is_count_valid(neigh)] f_neighs = get_neigh_idxs(first) s_neighs = get_neigh_idxs(second) a = set(f_neighs) - {first.GetIdx(), second.GetIdx()} b = set(s_neighs) - {first.GetIdx(), second.GetIdx()} others = list(a.intersection(b)) if len(others) == 0: # is disjoined return None else: return list(others)
def _is_square(self, first: Chem.Atom, second: Chem.Atom) -> bool: """ Get bool of whether two atoms share a common over-neighbor. Ie. joining them would make a square. Direct bond does not count. :param first: :param second: :return: """ for third in [neigh for neigh in second.GetNeighbors() if neigh.GetIdx() != first.GetIdx()]: if self._is_triangle(first, third) is True: return True else: return False
def _add_bond_regardlessly(self, mol, first: Chem.Atom, second: Chem.Atom, bond_type, provenance='other_novel'): """ This methods does no checking and operates dangerously! :param mol: :param first: :param second: :param bond_type: :param provenance: :return: """ first_idx = first.GetIdx() second_idx = second.GetIdx() # add if absent... (error prevention) present_bond = mol.GetBondBetweenAtoms(first_idx, second_idx) if present_bond is None: mol.AddBond(first_idx, second_idx, bond_type) new_bond = mol.GetBondBetweenAtoms(first_idx, second_idx) BondProvenance.set_bond(new_bond, provenance)
def get_substructures_from_atom( atom: Chem.Atom, max_size: int, substructure: Set[int] = None) -> Set[FrozenSet[int]]: """ Recursively gets all substructures up to a maximum size starting from an atom in a substructure. :param atom: The atom to start at. :param max_size: The maximum size of the substructure to fine. :param substructure: The current substructure that atom is in. :return: A set of substructures starting at atom where each substructure is a frozenset of indices. """ assert max_size >= 1 if substructure is None: substructure = {atom.GetIdx()} substructures = {frozenset(substructure)} if len(substructure) == max_size: return substructures # Get neighbors which are not already in the substructure new_neighbors = [ neighbor for neighbor in atom.GetNeighbors() if neighbor.GetIdx() not in substructure ] for neighbor in new_neighbors: # Define new substructure with neighbor new_substructure = deepcopy(substructure) new_substructure.add(neighbor.GetIdx()) # Skip if new substructure has already been considered if frozenset(new_substructure) in substructures: continue # Recursively get substructures including this substructure plus neighbor new_substructures = get_substructures_from_atom( neighbor, max_size, new_substructure) # Add those substructures to current set of substructures substructures |= new_substructures return substructures
def atom_to_feat(self, atm: Chem.Atom, owning_mol: Chem.Mol, idx): # nb the func GetOwningMol could not be used for mol as would return different object each time this_atms_idx = atm.GetIdx() assert idx == this_atms_idx feat = torch.zeros(len(self), dtype=torch.float32) # One hot encoding of element try: feat[self.atms_to_idx[atm.GetSymbol()]] = 1. except KeyError as ex: warnings.warn(f"Ignoring the symbol {atm.GetSymbol()}") idx_up_to = self.number_atom_options # Atomic Number feat[idx_up_to] = float(atm.GetAtomicNum()) idx_up_to += 1 # Acceptor/donor acceptor_ids, donor_ids = self.get_acceptor_and_donor_ids(owning_mol) # Acceptor feat[idx_up_to] = float(this_atms_idx in acceptor_ids) idx_up_to += 1 # Donor feat[idx_up_to] = float(this_atms_idx in donor_ids) idx_up_to += 1 # Hybridization hyb_idx = self.hybridization(atm.GetHybridization()) if hyb_idx is not None: feat[idx_up_to + hyb_idx] = 1. idx_up_to += self.number_hyb_options # Aromatic feat[idx_up_to] = float(atm.GetIsAromatic()) idx_up_to += 1 # Number of Hydrogens feat[idx_up_to] = float(atm.GetNumImplicitHs()) idx_up_to += 1 return feat
def downgrade_ring(self, atom: Chem.Atom): ## very crappy way of doing this log.debug(f'downgrading whole ring!') atom.SetIsAromatic(False) ringinfo = self._get_ring_info(mode='atom') get_atomrings = lambda ai: [ring for ring in ringinfo if ai in ring] atomrings = get_atomrings(atom.GetIdx()) for atomring in atomrings: rnieghs = self._get_ring_neighbors(atomring) for n1, n2 in rnieghs: self.mol.GetAtomWithIdx(n1).SetIsAromatic(False) self.mol.GetAtomWithIdx(n2).SetIsAromatic(False) self.mol.GetBondBetweenAtoms(n1, n2).SetBondType( Chem.BondType.SINGLE) for atomring in atomrings: rnieghs = self._get_ring_neighbors(atomring) for n1, n2 in rnieghs: if self._get_valence_difference(self.mol.GetAtomWithIdx(n1)) <= -2 and \ self._get_valence_difference(self.mol.GetAtomWithIdx(n2)) <= -2: self.mol.GetBondBetweenAtoms(n1, n2).SetBondType( Chem.BondType.DOUBLE)
def _add_bond_if_possible(self, mol, first: Chem.Atom, second: Chem.Atom, provenance='other_novel'): """ This method is used by _copy_bonding, but triggered when force=False :param mol: :param first: :param second: :param provenance: :return: """ # --- Prep first_idx = first.GetIdx() second_idx = second.GetIdx() present_bond = mol.GetBondBetweenAtoms(first_idx, second_idx) if second.HasProp('_ring_bond'): bt = getattr(Chem.BondType, second.GetProp('_ring_bond')) else: bt = None # === Bond already exists series ============================================ # --- Bond already exists and not bond type is specified if present_bond is not None and bt is None: self.journal.debug( f'Bond between {first_idx} and {second_idx} already exists') return True # exists # --- Bond already exists but has an error elif present_bond is not None and present_bond.GetBondType() is None: present_bond.SetBondType(Chem.BondType.SINGLE) return True # --- Bond already exists and matches the expected bond type elif present_bond is not None and bt.name == present_bond.GetBondType( ).name: return True # --- Bond already exists but does not match the expected bond type elif present_bond is not None: present_bond.SetBondType(bt) return True # === Assess if new bond should be added ============================================ # --- Don't add if it would make a triangle elif self._is_would_be_triangle(first, second): self.journal.debug( f'Bond between {first_idx} and {second_idx} would make a triangle, skipping' ) return False # --- Don't add if it would make a square elif self._is_would_be_square(first, second): self.journal.debug( f'Bond between {first_idx} and {second_idx} would make a square, skipping' ) return False # --- Don't add if it would ruin the warhead elif self._is_connected_warhead(second, first): self.journal.debug( f'Bond between {first_idx} and {second_idx} would break a warhead, skipping' ) return False # --- New bond is green lit elif self._assess_atom_for_possible_bonding( first, bt) and self._assess_atom_for_possible_bonding( second, bt): mol.AddBond(first_idx, second_idx, bt if bt is not None else Chem.BondType.SINGLE) new_bond = mol.GetBondBetweenAtoms(first_idx, second_idx) BondProvenance.set_bond(new_bond, provenance) return True # --- New bond is no go else: # len(Chem.GetMolFrags(mol, sanitizeFrags=False)) ought to be checked. # however, join_neighboring gets called by emergency bonding so should be fine. return False # too bonded etc.
def atom_coords(atom: Chem.Atom, conf: Chem.Conformer) -> List[float]: """Returns the x, y, and z coordinates of an atom in a molecule conformer. """ p = conf.GetAtomPosition(atom.GetIdx()) fts = [p.x, p.y, p.z] return fts
def index(atom: Atom) -> int: """Index within the parent molecule (int). """ return atom.GetIdx()