def calculateBondSymmetryNumber(molecule, atom1, atom2): """ Return the symmetry number centered at `bond` in the structure. """ bond = atom1.edges[atom2] symmetryNumber = 1 if bond.isSingle() or bond.isDouble() or bond.isTriple(): if atom1.equivalent(atom2): # An O-O bond is considered to be an "optical isomer" and so no # symmetry correction will be applied if atom1.atomType.label == 'Os' and atom2.atomType.label == 'Os' and atom1.radicalElectrons == atom2.radicalElectrons == 0: return symmetryNumber # If the molecule is diatomic, then we don't have to check the # ligands on the two atoms in this bond (since we know there # aren't any) elif len(molecule.vertices) == 2: symmetryNumber = 2 else: molecule.removeBond(bond) structure = molecule.copy(True) molecule.addBond(bond) atom1 = structure.atoms[molecule.atoms.index(atom1)] atom2 = structure.atoms[molecule.atoms.index(atom2)] fragments = structure.split() if len(fragments) != 2: return symmetryNumber fragment1, fragment2 = fragments if atom1 in fragment1.atoms: fragment1.removeAtom(atom1) if atom2 in fragment1.atoms: fragment1.removeAtom(atom2) if atom1 in fragment2.atoms: fragment2.removeAtom(atom1) if atom2 in fragment2.atoms: fragment2.removeAtom(atom2) groups1 = fragment1.split() groups2 = fragment2.split() # Test functional groups for symmetry if len(groups1) == len(groups2) == 1: if groups1[0].isIsomorphic(groups2[0]): symmetryNumber *= 2 elif len(groups1) == len(groups2) == 2: if groups1[0].isIsomorphic(groups2[0]) and groups1[1].isIsomorphic(groups2[1]): symmetryNumber *= 2 elif groups1[1].isIsomorphic(groups2[0]) and groups1[0].isIsomorphic(groups2[1]): symmetryNumber *= 2 elif len(groups1) == len(groups2) == 3: if groups1[0].isIsomorphic(groups2[0]) and groups1[1].isIsomorphic(groups2[1]) and groups1[2].isIsomorphic(groups2[2]): symmetryNumber *= 2 elif groups1[0].isIsomorphic(groups2[0]) and groups1[1].isIsomorphic(groups2[2]) and groups1[2].isIsomorphic(groups2[1]): symmetryNumber *= 2 elif groups1[0].isIsomorphic(groups2[1]) and groups1[1].isIsomorphic(groups2[2]) and groups1[2].isIsomorphic(groups2[0]): symmetryNumber *= 2 elif groups1[0].isIsomorphic(groups2[1]) and groups1[1].isIsomorphic(groups2[0]) and groups1[2].isIsomorphic(groups2[2]): symmetryNumber *= 2 elif groups1[0].isIsomorphic(groups2[2]) and groups1[1].isIsomorphic(groups2[0]) and groups1[2].isIsomorphic(groups2[1]): symmetryNumber *= 2 elif groups1[0].isIsomorphic(groups2[2]) and groups1[1].isIsomorphic(groups2[1]) and groups1[2].isIsomorphic(groups2[0]): symmetryNumber *= 2 return symmetryNumber
def calculateBondSymmetryNumber(molecule, atom1, atom2): """ Return the symmetry number centered at `bond` in the structure. """ bond = atom1.edges[atom2] symmetryNumber = 1 if atom1.equivalent(atom2): # An O-O bond is considered to be an "optical isomer" and so no # symmetry correction will be applied if atom1.atomType.label == 'O2s' and atom2.atomType.label == 'O2s' and atom1.radicalElectrons == atom2.radicalElectrons == 0: return symmetryNumber # If the molecule is diatomic, then we don't have to check the # ligands on the two atoms in this bond (since we know there # aren't any) elif len(molecule.vertices) == 2: symmetryNumber = 2 else: molecule.removeBond(bond) structure = molecule.copy(True) molecule.addBond(bond) atom1 = structure.atoms[molecule.atoms.index(atom1)] atom2 = structure.atoms[molecule.atoms.index(atom2)] fragments = structure.split() if len(fragments) != 2: return symmetryNumber fragment1, fragment2 = fragments if atom1 in fragment1.atoms: fragment1.removeAtom(atom1) if atom2 in fragment1.atoms: fragment1.removeAtom(atom2) if atom1 in fragment2.atoms: fragment2.removeAtom(atom1) if atom2 in fragment2.atoms: fragment2.removeAtom(atom2) groups1 = fragment1.split() groups2 = fragment2.split() # Test functional groups for symmetry if len(groups1) == len(groups2) == 1: if groups1[0].isIsomorphic(groups2[0]): symmetryNumber *= 2 elif len(groups1) == len(groups2) == 2: if groups1[0].isIsomorphic(groups2[0]) and groups1[1].isIsomorphic(groups2[1]): symmetryNumber *= 2 elif groups1[1].isIsomorphic(groups2[0]) and groups1[0].isIsomorphic(groups2[1]): symmetryNumber *= 2 elif len(groups1) == len(groups2) == 3: if groups1[0].isIsomorphic(groups2[0]) and groups1[1].isIsomorphic(groups2[1]) and groups1[2].isIsomorphic(groups2[2]): symmetryNumber *= 2 elif groups1[0].isIsomorphic(groups2[0]) and groups1[1].isIsomorphic(groups2[2]) and groups1[2].isIsomorphic(groups2[1]): symmetryNumber *= 2 elif groups1[0].isIsomorphic(groups2[1]) and groups1[1].isIsomorphic(groups2[2]) and groups1[2].isIsomorphic(groups2[0]): symmetryNumber *= 2 elif groups1[0].isIsomorphic(groups2[1]) and groups1[1].isIsomorphic(groups2[0]) and groups1[2].isIsomorphic(groups2[2]): symmetryNumber *= 2 elif groups1[0].isIsomorphic(groups2[2]) and groups1[1].isIsomorphic(groups2[0]) and groups1[2].isIsomorphic(groups2[1]): symmetryNumber *= 2 elif groups1[0].isIsomorphic(groups2[2]) and groups1[1].isIsomorphic(groups2[1]) and groups1[2].isIsomorphic(groups2[0]): symmetryNumber *= 2 return symmetryNumber
def calculateAxisSymmetryNumber(molecule): """ Get the axis symmetry number correction. The "axis" refers to a series of two or more cumulated double bonds (e.g. C=C=C, etc.). Corrections for single C=C bonds are handled in getBondSymmetryNumber(). Each axis (C=C=C) has the potential to double the symmetry number. If an end has 0 or 1 groups (eg. =C=CJJ or =C=C-R) then it cannot alter the axis symmetry and is disregarded:: A=C=C=C.. A-C=C=C=C-A s=1 s=1 If an end has 2 groups that are different then it breaks the symmetry and the symmetry for that axis is 1, no matter what's at the other end:: A\ A\ /A T=C=C=C=C-A T=C=C=C=T B/ A/ \B s=1 s=1 If you have one or more ends with 2 groups, and neither end breaks the symmetry, then you have an axis symmetry number of 2:: A\ /B A\ C=C=C=C=C C=C=C=C-B A/ \B A/ s=2 s=2 """ symmetryNumber = 1 # List all double bonds in the structure doubleBonds = [] for atom1 in molecule.vertices: for atom2 in atom1.edges: if atom1.edges[atom2].isDouble() and molecule.vertices.index(atom1) < molecule.vertices.index(atom2): doubleBonds.append((atom1, atom2)) # Search for adjacent double bonds cumulatedBonds = [] for i, bond1 in enumerate(doubleBonds): atom11, atom12 = bond1 for bond2 in doubleBonds[i + 1 :]: atom21, atom22 = bond2 if atom11 is atom21 or atom11 is atom22 or atom12 is atom21 or atom12 is atom22: listToAddTo = None for cumBonds in cumulatedBonds: if (atom11, atom12) in cumBonds or (atom21, atom22) in cumBonds: listToAddTo = cumBonds if listToAddTo is not None: if (atom11, atom12) not in listToAddTo: listToAddTo.append((atom11, atom12)) if (atom21, atom22) not in listToAddTo: listToAddTo.append((atom21, atom22)) else: cumulatedBonds.append([(atom11, atom12), (atom21, atom22)]) # Also keep isolated double bonds for bond1 in doubleBonds: for bonds in cumulatedBonds: if bond1 in bonds: break else: cumulatedBonds.append([bond1]) # For each set of adjacent double bonds, check for axis symmetry for bonds in cumulatedBonds: # Do nothing if less than two cumulated bonds if len(bonds) < 1: continue # Do nothing if axis is in cycle found = False for atom1, atom2 in bonds: if molecule.isBondInCycle(atom1.edges[atom2]): found = True if found: continue # Find terminal atoms in axis # Terminal atoms labelled T: T=C=C=C=T axis = [] for bond in bonds: axis.extend(bond) terminalAtoms = [] for atom in axis: if axis.count(atom) == 1: terminalAtoms.append(atom) if len(terminalAtoms) != 2: continue # Remove axis from (copy of) structure bondlist = [] for atom1, atom2 in bonds: bond = atom1.edges[atom2] bondlist.append(bond) molecule.removeBond(bond) structure = molecule.copy(True) terminalAtoms = [structure.vertices[molecule.vertices.index(atom)] for atom in terminalAtoms] for bond in bondlist: molecule.addBond(bond) atomsToRemove = [] for atom in structure.vertices: if len(atom.edges) == 0 and atom not in terminalAtoms: # it's not bonded to anything atomsToRemove.append(atom) for atom in atomsToRemove: structure.removeAtom(atom) # Split remaining fragments of structure end_fragments = structure.split() # # there can be two groups at each end A\ /B # T=C=C=C=T # A/ \B # to start with nothing has broken symmetry about the axis symmetry_broken = False end_fragments_to_remove = [] for fragment in end_fragments: # a fragment is one end of the axis # remove the atom that was at the end of the axis and split what's left into groups terminalAtom = None for atom in terminalAtoms: if atom in fragment.atoms: terminalAtom = atom fragment.removeAtom(atom) break else: continue groups = [] if len(fragment.atoms) > 0: groups = fragment.split() # If end has only one group then it can't contribute to (nor break) axial symmetry # Eg. this has no axis symmetry: A-T=C=C=C=T-A # so we remove this end from the list of interesting end fragments if len(groups) == 0: end_fragments_to_remove.append(fragment) continue # next end fragment elif len(groups) == 1 and terminalAtom.radicalElectrons == 0: if terminalAtom.atomType.label == "N3d": symmetry_broken = True else: end_fragments_to_remove.append(fragment) continue # next end fragment elif len(groups) == 1 and terminalAtom.radicalElectrons != 0: symmetry_broken = True elif len(groups) == 2: if not groups[0].isIsomorphic(groups[1]): # this end has broken the symmetry of the axis symmetry_broken = True for fragment in end_fragments_to_remove: end_fragments.remove(fragment) # If there are end fragments left that can contribute to symmetry, # and none of them broke it, then double the symmetry number # NB>> This assumes coordination number of 4 (eg. Carbon). # And would be wrong if we had /B # =C=C=C=C=T-B # \B # (for some T with coordination number 5). if end_fragments and not symmetry_broken: symmetryNumber *= 2 return symmetryNumber
def calculateAxisSymmetryNumber(molecule): """ Get the axis symmetry number correction. The "axis" refers to a series of two or more cumulated double bonds (e.g. C=C=C, etc.). Corrections for single C=C bonds are handled in getBondSymmetryNumber(). Each axis (C=C=C) has the potential to double the symmetry number. If an end has 0 or 1 groups (eg. =C=CJJ or =C=C-R) then it cannot alter the axis symmetry and is disregarded:: A=C=C=C.. A-C=C=C=C-A s=1 s=1 If an end has 2 groups that are different then it breaks the symmetry and the symmetry for that axis is 1, no matter what's at the other end:: A\ A\ /A T=C=C=C=C-A T=C=C=C=T B/ A/ \B s=1 s=1 If you have one or more ends with 2 groups, and neither end breaks the symmetry, then you have an axis symmetry number of 2:: A\ /B A\ C=C=C=C=C C=C=C=C-B A/ \B A/ s=2 s=2 """ symmetryNumber = 1 # List all double bonds in the structure doubleBonds = [] for atom1 in molecule.vertices: for atom2 in atom1.edges: if (atom1.edges[atom2].isDouble() or atom1.edges[atom2].order > 2) \ and molecule.vertices.index(atom1) < molecule.vertices.index(atom2): doubleBonds.append((atom1, atom2)) # Search for adjacent double bonds cumulatedBonds = [] for i, bond1 in enumerate(doubleBonds): atom11, atom12 = bond1 for bond2 in doubleBonds[i + 1:]: atom21, atom22 = bond2 if atom11 is atom21 or atom11 is atom22 or atom12 is atom21 or atom12 is atom22: listToAddTo = None for cumBonds in cumulatedBonds: if (atom11, atom12) in cumBonds or (atom21, atom22) in cumBonds: listToAddTo = cumBonds if listToAddTo is not None: if (atom11, atom12) not in listToAddTo: listToAddTo.append((atom11, atom12)) if (atom21, atom22) not in listToAddTo: listToAddTo.append((atom21, atom22)) else: cumulatedBonds.append([(atom11, atom12), (atom21, atom22)]) # Also keep isolated double bonds for bond1 in doubleBonds: for bonds in cumulatedBonds: if bond1 in bonds: break else: cumulatedBonds.append([bond1]) # For each set of adjacent double bonds, check for axis symmetry for bonds in cumulatedBonds: # Do nothing if less than two cumulated bonds if len(bonds) < 1: continue # Do nothing if axis is in cycle found = False for atom1, atom2 in bonds: if molecule.isBondInCycle(atom1.edges[atom2]): found = True if found: continue # Find terminal atoms in axis # Terminal atoms labelled T: T=C=C=C=T axis = [] for bond in bonds: axis.extend(bond) terminalAtoms = [] for atom in axis: if axis.count(atom) == 1: terminalAtoms.append(atom) if len(terminalAtoms) != 2: continue # Remove axis from (copy of) structure bondlist = [] for atom1, atom2 in bonds: bond = atom1.edges[atom2] bondlist.append(bond) molecule.removeBond(bond) structure = molecule.copy(True) terminalAtoms = [ structure.vertices[molecule.vertices.index(atom)] for atom in terminalAtoms ] for bond in bondlist: molecule.addBond(bond) atomsToRemove = [] for atom in structure.vertices: if len( atom.edges ) == 0 and atom not in terminalAtoms: # it's not bonded to anything atomsToRemove.append(atom) for atom in atomsToRemove: structure.removeAtom(atom) # Split remaining fragments of structure end_fragments = structure.split() # # there can be two groups at each end A\ /B # T=C=C=C=T # A/ \B # to start with nothing has broken symmetry about the axis symmetry_broken = False end_fragments_to_remove = [] for fragment in end_fragments: # a fragment is one end of the axis # remove the atom that was at the end of the axis and split what's left into groups terminalAtom = None for atom in terminalAtoms: if atom in fragment.atoms: terminalAtom = atom fragment.removeAtom(atom) break else: continue groups = [] if len(fragment.atoms) > 0: groups = fragment.split() # If end has only one group then it can't contribute to (nor break) axial symmetry # Eg. this has no axis symmetry: A-T=C=C=C=T-A # so we remove this end from the list of interesting end fragments if len(groups) == 0: end_fragments_to_remove.append(fragment) continue # next end fragment elif len(groups) == 1 and terminalAtom.radicalElectrons == 0: if terminalAtom.atomType.label == 'N3d': symmetry_broken = True else: end_fragments_to_remove.append(fragment) continue # next end fragment elif len(groups) == 1 and terminalAtom.radicalElectrons != 0: symmetry_broken = True elif len(groups) == 2: if not groups[0].isIsomorphic(groups[1]): # this end has broken the symmetry of the axis symmetry_broken = True for fragment in end_fragments_to_remove: end_fragments.remove(fragment) # If there are end fragments left that can contribute to symmetry, # and none of them broke it, then double the symmetry number # NB>> This assumes coordination number of 4 (eg. Carbon). # And would be wrong if we had /B # =C=C=C=C=T-B # \B # (for some T with coordination number 5). if end_fragments and not symmetry_broken: symmetryNumber *= 2 return symmetryNumber