def beta_atoms(atom, nitrogen, imine, in_ring, begin_end): for bond in openbabel.OBAtomBondIter(atom): begin_atom = bond.GetBeginAtom() end_atom = bond.GetEndAtom() if begin_atom != nitrogen and begin_atom != atom: if in_ring == True and begin_end is "begin": imine.insert(0, begin_atom.GetIdx()) elif in_ring == True and begin_end is "end": imine.append(begin_atom.GetIdx()) elif in_ring == False and begin_end is "begin": imine.append(begin_atom.GetIdx()) else: imine.insert(0, begin_atom.GetIdx()) return imine if end_atom != nitrogen and end_atom != atom: if in_ring == True and begin_end is "begin": imine.insert(0, end_atom.GetIdx()) elif in_ring == True and begin_end is "end": imine.append(end_atom.GetIdx()) elif in_ring == False and begin_end is "begin": imine.append(end_atom.GetIdx()) else: imine.insert(0, begin_atom.GetIdx()) return imine return imine
def remove_bad_valences(self, ob_mol, atoms, struct): ''' Remove hypervalent bonds without fragmenting the molecule, and prioritize stretched bonds. Also remove bonds between halogens/hydrogens. ''' # get max valence of the atoms max_vals = get_max_valences(atoms) # remove any bonds between halogens or hydrogens for bond in ob.OBMolBondIter(ob_mol): atom_a = bond.GetBeginAtom() atom_b = bond.GetEndAtom() if (max_vals.get(atom_a.GetIdx(), 1) == 1 and max_vals.get(atom_b.GetIdx(), 1) == 1): ob_mol.DeleteBond(bond) # remove bonds causing larger-than-permitted valences # prioritize atoms with lowest max valence, since they # place the hardest constraint on reachability (e.g O) atom_info = sort_atoms_by_valence(atoms, max_vals) for max_val, rem_val, atom in atom_info: if atom.GetExplicitValence() <= max_val: continue # else, the atom could have an invalid valence # so check whether we can modify a bond bond_info = sort_bonds_by_stretch(ob.OBAtomBondIter(atom)) for bond_stretch, bond_len, bond in bond_info: # do the atoms involved in this bond have bad valences? # since we are modifying the valences in the loop, this # could have changed since calling sort_atoms_by_valence a1, a2 = bond.GetBeginAtom(), bond.GetEndAtom() max_val_diff = max( # by how much are the valences over? a1.GetExplicitValence() - max_vals.get(a1.GetIdx(), 1), a2.GetExplicitValence() - max_vals.get(a2.GetIdx(), 1)) if max_val_diff > 0: bond_order = bond.GetBondOrder() if bond_order > max_val_diff: # decrease bond order bond.SetBondOrder(bond_order - max_val_diff) elif reachable(a1, a2): # don't fragment the molecule ob_mol.DeleteBond(bond) # if the current atom now has a permitted valence, # break and let other atoms choose next bonds to remove if atom.GetExplicitValence() <= max_vals[atom.GetIdx()]: break # deleting bonds resets this flag ob_mol.SetHybridizationPerceived(True)
def test_atom_iteration(self): mol = parse_smiles("[U](F)(F)(F)[Cl]") atom = mol.GetAtom(1) counts = {9: 0, 17: 0} for bond in ob.OBAtomBondIter(atom): xatom = bond.GetNbrAtom(atom) n = xatom.GetAtomicNum() counts[n] += 1 self.assertEqual(counts, {9: 3, 17: 1}) counts = {9: 0, 17: 0} for atom in ob.OBAtomAtomIter(atom): n = atom.GetAtomicNum() counts[n] += 1 self.assertEqual(counts, {9: 3, 17: 1})
def visit_mol(mol, msg): mol = copy_ob_mol(mol) visited_mols.append(mol) if self.debug: bmap = {1: '-', 2: '=', 3: '≡'} print(len(visited_mols), msg) assert (mol.HasHybridizationPerceived() and mol.HasAromaticPerceived()), 'perception is on' return for a in ob.OBMolAtomIter(mol): print(' ', (a.GetAtomicNum(), a.IsAromatic(), a.GetHyb(), a.GetImplicitHCount()), end=' ') for b in ob.OBAtomBondIter(a): print('({}{}{})'.format(b.GetBeginAtomIdx(), bmap[b.GetBondOrder()], b.GetEndAtomIdx()), end=' ') print()
def test_rings(self): mol = parse_smiles("C12CNCC3C1.C2CCC3") atom = mol.GetAtom(1) self.assertTrue(atom.IsInRing()) self.assertTrue(atom.IsInRingSize(6)) self.assertTrue(atom.IsInRingSize(7)) self.assertFalse(atom.IsInRingSize(10)) self.assertEqual(atom.MemberOfRingCount(), 2) self.assertEqual(atom.MemberOfRingSize(), 6) self.assertEqual(atom.CountRingBonds(), 3) sssr = mol.GetSSSR() self.assertEqual(len(sssr), 2) ring_info = [(ring.Size(), ring) for ring in sssr] ring_info.sort() sizes = [x[0] for x in ring_info] self.assertEqual(sizes, [6, 7]) ring = ring_info[0][1] self.assertFalse(ring.IsAromatic()) self.assertEqual(ring.GetType(), "") # XXX *which* of the non-carbons is the root? That isn't documented idx = ring.GetRootAtom() # Shouldn't that be "Idx"? # Since there's only one non-C, it must be the N atom = mol.GetAtom(idx) self.assertEqual(atom.GetAtomicNum(), 7) self.assertTrue(ring.IsMember(atom)) for bond in ob.OBAtomBondIter(atom): self.assertTrue(ring.IsMember(bond)) self.assertTrue(ring.IsInRing(idx)) lssr = mol.GetLSSR() self.assertEqual(len(lssr), 2) sizes = [ring.Size() for ring in lssr] sizes.sort() self.assertEqual(sizes, [6, 7])
def connect_the_dots(mol, atoms, struct, maxbond=4): '''Custom implementation of ConnectTheDots. This is similar to OpenBabel's version, but is more willing to make long bonds (up to maxbond long) to keep the molecule connected. It also attempts to respect atom type information from struct. atoms and struct need to correspond in their order Assumes no hydrogens or existing bonds. ''' pt = Chem.GetPeriodicTable() if len(atoms) == 0: return mol.BeginModify() #just going to to do n^2 comparisons, can worry about efficiency later coords = np.array([(a.GetX(), a.GetY(), a.GetZ()) for a in atoms]) dists = squareform(pdist(coords)) types = [struct.channels[t].name for t in struct.c] for (i, a) in enumerate(atoms): for (j, b) in enumerate(atoms): if a == b: break if dists[i, j] < 0.01: #reduce from 0.4 continue #don't bond too close atoms if dists[i, j] < maxbond: flag = 0 if 'Aromatic' in types[i] and 'Aromatic' in types[j]: flag = ob.OB_AROMATIC_BOND mol.AddBond(a.GetIdx(), b.GetIdx(), 1, flag) atom_maxb = {} for (i, a) in enumerate(atoms): #set max valance to the smallest max allowed by openbabel or rdkit #since we want the molecule to be valid for both (rdkit is usually lower) maxb = ob.GetMaxBonds(a.GetAtomicNum()) maxb = min(maxb, pt.GetDefaultValence(a.GetAtomicNum())) if 'Donor' in types[i]: maxb -= 1 #leave room for hydrogen atom_maxb[a.GetIdx()] = maxb #remove any impossible bonds between halogens for bond in ob.OBMolBondIter(mol): a1 = bond.GetBeginAtom() a2 = bond.GetEndAtom() if atom_maxb[a1.GetIdx()] == 1 and atom_maxb[a2.GetIdx()] == 1: mol.DeleteBond(bond) def get_bond_info(biter): '''Return bonds sorted by their distortion''' bonds = [b for b in biter] binfo = [] for bond in bonds: bdist = bond.GetLength() #compute how far away from optimal we are a1 = bond.GetBeginAtom() a2 = bond.GetEndAtom() ideal = ob.GetCovalentRad(a1.GetAtomicNum()) + ob.GetCovalentRad( a2.GetAtomicNum()) stretch = bdist - ideal binfo.append((stretch, bdist, bond)) binfo.sort(reverse=True, key=lambda t: t[:2]) #most stretched bonds first return binfo #prioritize removing hypervalency causing bonds, do more valent #constrained atoms first since their bonds introduce the most problems #with reachability (e.g. oxygen) hypers = sorted([(atom_maxb[a.GetIdx()], a.GetExplicitValence() - atom_maxb[a.GetIdx()], a) for a in atoms], key=lambda aa: (aa[0], -aa[1])) for mb, diff, a in hypers: if a.GetExplicitValence() <= atom_maxb[a.GetIdx()]: continue binfo = get_bond_info(ob.OBAtomBondIter(a)) for stretch, bdist, bond in binfo: #can we remove this bond without disconnecting the molecule? a1 = bond.GetBeginAtom() a2 = bond.GetEndAtom() #get right valence if a1.GetExplicitValence() > atom_maxb[a1.GetIdx()] or \ a2.GetExplicitValence() > atom_maxb[a2.GetIdx()]: #don't fragment the molecule if not reachable(a1, a2): continue mol.DeleteBond(bond) if a.GetExplicitValence() <= atom_maxb[a.GetIdx()]: break #let nbr atoms choose what bonds to throw out binfo = get_bond_info(ob.OBMolBondIter(mol)) #now eliminate geometrically poor bonds for stretch, bdist, bond in binfo: #can we remove this bond without disconnecting the molecule? a1 = bond.GetBeginAtom() a2 = bond.GetEndAtom() #as long as we aren't disconnecting, let's remove things #that are excessively far away (0.45 from ConnectTheDots) #get bonds to be less than max allowed #also remove tight angles, because that is what ConnectTheDots does if stretch > 0.45 or forms_small_angle(a1, a2) or forms_small_angle( a2, a1): #don't fragment the molecule if not reachable(a1, a2): continue mol.DeleteBond(bond) mol.EndModify()
def fill_rem_valences(self, ob_mol, atoms, struct): ''' Fill empty valences with hydrogens up to the amount expected by the atom type, or a typical amount according to openbabel, and then fill remaining empty valences with higher bond orders. ''' max_vals = get_max_valences(atoms) for ob_atom, atom_type in zip(atoms, struct.atom_types): if struct.typer.explicit_h: # all Hs should already be present continue max_val = max_vals.get(ob_atom.GetIdx(), 1) if Atom.h_count in struct.typer: # this should have already been set # by set_min_h_counts, but whatever h_count = Atom.h_count(ob_atom) if h_count < atom_type.h_count: n = ob_atom.GetImplicitHCount() ob_atom.SetImplicitHCount(n + atom_type.h_count - h_count) elif ob_atom.GetExplicitValence() < max_val: # this uses explicit valence and formal charge, # and only ever INCREASES hydrogens, since it # never sets implicit H to a negative value # but it does overwrite the existing value, so # we need to save it beforehand and then add n = ob_atom.GetImplicitHCount() ob.OBAtomAssignTypicalImplicitHydrogens(ob_atom) n += ob_atom.GetImplicitHCount() ob_atom.SetImplicitHCount(n) # these have possibly changed max_vals = get_max_valences(atoms) # now increment bond orders to fill remaining valences atom_info = sort_atoms_by_valence(atoms, max_vals) for max_val, rem_val, atom in reversed(atom_info): if atom.GetExplicitValence() >= max_val: continue # else, the atom could have an empty valence # so check whether we can augment a bond, # prioritizing bonds that are too short bond_info = sort_bonds_by_stretch(ob.OBAtomBondIter(atom)) for bond_stretch, bond_len, bond in reversed(bond_info): if bond.GetBondOrder() >= 3: continue # don't go above triple # do the atoms involved in this bond have empty valences? # since we are modifying the valences in the loop, this # could have changed since calling sort_atoms_by_valence a1, a2 = bond.GetBeginAtom(), bond.GetEndAtom() min_val_diff = min( # by how much are the valences under? max_vals.get(a1.GetIdx(), 1) - a1.GetExplicitValence(), max_vals.get(a2.GetIdx(), 1) - a2.GetExplicitValence()) if min_val_diff > 0: # increase bond order bond_order = bond.GetBondOrder() # don't go above triple bond.SetBondOrder(min(bond_order + min_val_diff, 3)) # if the current atom now has its preferred valence, # break and let other atoms choose next bonds to augment if atom.GetExplicitValence() == max_vals[atom.GetIdx()]: break
def array_of_imine_torsions(imine_nitrogens, molecule): def beta_atoms(atom, nitrogen, imine, in_ring, begin_end): for bond in openbabel.OBAtomBondIter(atom): begin_atom = bond.GetBeginAtom() end_atom = bond.GetEndAtom() if begin_atom != nitrogen and begin_atom != atom: if in_ring == True and begin_end is "begin": imine.insert(0, begin_atom.GetIdx()) elif in_ring == True and begin_end is "end": imine.append(begin_atom.GetIdx()) elif in_ring == False and begin_end is "begin": imine.append(begin_atom.GetIdx()) else: imine.insert(0, begin_atom.GetIdx()) return imine if end_atom != nitrogen and end_atom != atom: if in_ring == True and begin_end is "begin": imine.insert(0, end_atom.GetIdx()) elif in_ring == True and begin_end is "end": imine.append(end_atom.GetIdx()) elif in_ring == False and begin_end is "begin": imine.append(end_atom.GetIdx()) else: imine.insert(0, begin_atom.GetIdx()) return imine return imine def get_torsions(imine_struct): torsion_list = list() for torsion in imine_struct: atom1 = molecule.GetAtom(torsion[0]) atom2 = molecule.GetAtom(torsion[1]) atom3 = molecule.GetAtom(torsion[2]) atom4 = molecule.GetAtom(torsion[3]) angle = molecule.GetTorsion(atom1, atom2, atom3, atom4) torsion_list.append(angle) torsion_array = np.array(torsion_list, dtype='float') return torsion_array imine_struct = list() for nitrogen in imine_nitrogens: imine = list() imine.append(nitrogen.GetIdx()) for bond in openbabel.OBAtomBondIter(nitrogen): begin_atom = bond.GetBeginAtom() end_atom = bond.GetEndAtom() if begin_atom != nitrogen: in_ring = begin_atom.MemberOfRingSize() != 0 begin_end = "begin" if in_ring == True: imine.insert(0, begin_atom.GetIdx()) imine = beta_atoms(begin_atom, nitrogen, imine, in_ring, begin_end) else: imine.append(begin_atom.GetIdx()) if end_atom != nitrogen: in_ring = end_atom.MemberOfRingSize() != 0 begin_end = "end" if in_ring == True: imine.append(end_atom.GetIdx()) imine = beta_atoms(end_atom, nitrogen, imine, in_ring, begin_end) else: imine.insert(0, end_atom.GetIdx()) imine_struct.append(imine) torsion_list = get_torsions(imine_struct) return torsion_list