def read_double_bond_stereo_layer( self): def get_lowest_numbered_neighbor( atom, verbotten_atom): neighs = [(n.properties_.get('inchi_number',1000000), n) for n in atom.neighbors if n is not verbotten_atom and "inchi_number" in n.properties_] if neighs: neighs.sort( reverse=True) return neighs[0][1] else: # we take the only one that is there (if any) neighs = [n for n in atom.neighbors if n is not verbotten_atom] if neighs: return neighs[0] else: raise oasa_inchi_error( "No neigbors on atom with stereo information!") layer = self.get_layer( "b") if not layer: return for a1,a2,sign in re.findall( "(\d+)-(\d+)([-+?u])", layer): atom1 = self.get_atom_with_inchi_number( int( a1)) atom2 = self.get_atom_with_inchi_number( int( a2)) bond = self.structure.get_edge_between( atom1, atom2) # we need neighbors with lowest inchi_number neigh1 = get_lowest_numbered_neighbor( atom1, atom2) neigh2 = get_lowest_numbered_neighbor( atom2, atom1) st = cis_trans_stereochemistry( references=[neigh1,atom1,atom2,neigh2], value=self.stereo_remap[sign]) self.structure.add_stereochemistry( st)
def read_double_bond_stereo_layer(self): def get_lowest_numbered_neighbor(atom, verbotten_atom): neighs = [ (n.properties_.get('inchi_number', 1000000), n) for n in atom.neighbors if n is not verbotten_atom and "inchi_number" in n.properties_ ] if neighs: neighs.sort(reverse=True) return neighs[0][1] else: # we take the only one that is there (if any) neighs = [n for n in atom.neighbors if n is not verbotten_atom] if neighs: return neighs[0] else: raise oasa_inchi_error( "No neigbors on atom with stereo information!") layer = self.get_layer("b") if not layer: return for a1, a2, sign in re.findall("(\d+)-(\d+)([-+?u])", layer): atom1 = self.get_atom_with_inchi_number(int(a1)) atom2 = self.get_atom_with_inchi_number(int(a2)) bond = self.structure.get_edge_between(atom1, atom2) # we need neighbors with lowest inchi_number neigh1 = get_lowest_numbered_neighbor(atom1, atom2) neigh2 = get_lowest_numbered_neighbor(atom2, atom1) st = cis_trans_stereochemistry( references=[neigh1, atom1, atom2, neigh2], value=self.stereo_remap[sign]) self.structure.add_stereochemistry(st)
def detect_stereochemistry_from_coords(self, omit_rings=True): import stereochemistry, geometry def add_neighbor_double_bonds(bond, path): for _e in bond.get_neighbor_edges(): if _e.order == 2 and _e not in path: path.append(_e) add_neighbor_double_bonds(_e, path) # double bonds # detect clusters of double bonds double_paths = [] processed = set() for e in self.edges: if e.order == 2 and e not in processed: if omit_rings and not self.is_edge_a_bridge(e): continue path = [e] add_neighbor_double_bonds(e, path) if len(path) % 2: double_paths.append(path) processed |= set(path) # detect config on these paths for path in double_paths: vertices = [] for bond in path: vertices.extend(bond.vertices) ends = [v for v in vertices if vertices.count(v) == 1] if len( ends ) != 2: # two ends is the only thing we are prepared to handle continue end1, end2 = ends # set stereochemistry for all neighbors of both ends for e1, n1 in end1.get_neighbor_edge_pairs(): plane1 = geometry.plane_normal_from_3_points( (n1.x, n1.y, n1.z), (end1.x, end1.y, end1.z), (end2.x, end2.y, end2.z)) if plane1 == None: continue # some coords were missing if not e1 in path: for e2, n2 in end2.get_neighbor_edge_pairs(): if not e2 in path: plane2 = geometry.plane_normal_from_3_points( (end1.x, end1.y, end1.z), (end2.x, end2.y, end2.z), (n2.x, n2.y, n2.z)) #cos_angle = geometry.same_or_oposite_side( plane1, plane2) cos_angle = geometry.angle_between_planes( plane1, plane2) if cos_angle < 0: value = stereochemistry.cis_trans_stereochemistry.OPPOSITE_SIDE else: value = stereochemistry.cis_trans_stereochemistry.SAME_SIDE if len(path) == 1: center = path[0] else: center = None refs = [n1, end1, end2, n2] st = stereochemistry.cis_trans_stereochemistry( center=center, value=value, references=refs) to_remove = None to_add = None for st1 in self.stereochemistry: if set(st1.references) == set(st.references): if st.value == st1.value: break else: to_remove = st1 break else: self.add_stereochemistry(st) if to_remove: self.remove_stereochemistry(to_remove)
def _process_stereochemistry( self, mol): ## process stereochemistry ## double bonds def get_stereobond_direction( end_atom, inside_atom, bond, init): position = mol.vertices.index( end_atom) - mol.vertices.index( inside_atom) char = bond.properties_['stereo'] == "\\" and 1 or -1 direction = (position * char * init) < 0 and "up" or "down" return direction def get_end_and_inside_vertex_from_edge_path( edge, path): a1,a2 = edge.vertices if len( [e for e in a1.neighbor_edges if e in path]) == 1: return a1, a2 return a2, a1 stereo_edges = [e for e in mol.edges if "stereo" in e.properties_] paths = [] for i,e1 in enumerate( stereo_edges): for e2 in stereo_edges[i+1:]: path = mol.get_path_between_edges( e1, e2) path2 = path[1:-1] if len( path2)%2 and not [_e for _e in path2 if _e.order != 2]: # only odd number of double bonds, double bonds only for _e in path[1:-1]: if not mol.is_edge_a_bridge_fast_and_dangerous( _e): break else: # only stereo related to non-cyclic bonds paths.append( path) for path in paths: bond1 = path[0] end_atom1,inside_atom1 = get_end_and_inside_vertex_from_edge_path( bond1, path) bond2 = path[-1] end_atom2,inside_atom2 = get_end_and_inside_vertex_from_edge_path( bond2, path) d1 = get_stereobond_direction( end_atom1, inside_atom1, bond1, -1) d2 = get_stereobond_direction( end_atom2, inside_atom2, bond2, -1) if d1 == d2: value = stereochemistry.cis_trans_stereochemistry.SAME_SIDE else: value = stereochemistry.cis_trans_stereochemistry.OPPOSITE_SIDE if len( path) == 3: center = path[1] else: center = None refs = [end_atom1,inside_atom1,inside_atom2,end_atom2] st = stereochemistry.cis_trans_stereochemistry( center=center, value=value, references=refs) mol.add_stereochemistry( st) # tetrahedral stereochemistry for v in mol.vertices: refs = None if 'stereo' in v.properties_: idx = [mol.vertices.index( n) for n in v.neighbors] idx.sort() if len( idx) < 3: pass # no stereochemistry with less then 3 neighbors elif len( idx) == 3: if v.explicit_hydrogens == 0: pass # no stereochemistry without adding hydrogen here else: if self.explicit_hydrogens_to_real_atoms: hs = mol.explicit_hydrogens_to_real_atoms( v) h = hs.pop() else: h = stereochemistry.explicit_hydrogen() v_idx = mol.vertices.index( v) idx1 = [i for i in idx if i < v_idx] idx2 = [i for i in idx if i > v_idx] refs = [mol.vertices[i] for i in idx1] + [h] + [mol.vertices[i] for i in idx2] elif len( idx) == 4: refs = [mol.vertices[i] for i in idx] else: pass # unhandled stereochemistry if refs: if v.properties_["stereo"] == "@": direction = stereochemistry.tetrahedral_stereochemistry.ANTICLOCKWISE elif v.properties_['stereo'] == "@@": direction = stereochemistry.tetrahedral_stereochemistry.CLOCKWISE else: continue # no meaning st = stereochemistry.tetrahedral_stereochemistry( center=v, value=direction, references=refs) mol.add_stereochemistry( st) # delete the data after processing for e in mol.edges: if 'stereo' in e.properties_: del e.properties_['stereo'] for v in mol.vertices: if 'stereo' in v.properties_: del v.properties_['stereo']
def detect_stereochemistry_from_coords( self, omit_rings=True): import stereochemistry,geometry def add_neighbor_double_bonds( bond, path): for _e in bond.get_neighbor_edges(): if _e.order == 2 and _e not in path: path.append( _e) add_neighbor_double_bonds( _e, path) # double bonds # detect clusters of double bonds double_paths = [] processed = set() for e in self.edges: if e.order == 2 and e not in processed: if omit_rings and not self.is_edge_a_bridge( e): continue path = [e] add_neighbor_double_bonds( e, path) if len( path) % 2: double_paths.append( path) processed |= set( path) # detect config on these paths for path in double_paths: vertices = [] for bond in path: vertices.extend( bond.vertices) ends = [v for v in vertices if vertices.count(v) == 1] if len( ends) != 2: # two ends is the only thing we are prepared to handle continue end1, end2 = ends # set stereochemistry for all neighbors of both ends for e1,n1 in end1.get_neighbor_edge_pairs(): plane1 = geometry.plane_normal_from_3_points( (n1.x,n1.y,n1.z),(end1.x,end1.y,end1.z),(end2.x,end2.y,end2.z)) if plane1 == None: continue # some coords were missing if not e1 in path: for e2,n2 in end2.get_neighbor_edge_pairs(): if not e2 in path: plane2 = geometry.plane_normal_from_3_points( (end1.x,end1.y,end1.z),(end2.x,end2.y,end2.z),(n2.x,n2.y,n2.z)) #cos_angle = geometry.same_or_oposite_side( plane1, plane2) cos_angle = geometry.angle_between_planes( plane1, plane2) if cos_angle < 0: value = stereochemistry.cis_trans_stereochemistry.OPPOSITE_SIDE else: value = stereochemistry.cis_trans_stereochemistry.SAME_SIDE if len( path) == 1: center = path[0] else: center = None refs = [n1,end1,end2,n2] st = stereochemistry.cis_trans_stereochemistry( center=center, value=value, references=refs) to_remove = None to_add = None for st1 in self.stereochemistry: if set( st1.references) == set( st.references): if st.value == st1.value: break else: to_remove = st1 break else: self.add_stereochemistry( st) if to_remove: self.remove_stereochemistry( to_remove)