def check_multiplicity(nRad, multiplicity): ''' Check that the multiplicity complies with the formula: m = 2s + 1, where s is the sum of the spin [+/- (1/2) ] of the unpaired electrons For a simple radical (nRad = 1): s = +1/2 , m = 2 (doublet) For a biradical, s can be either 0 [+0.5 + (-0.5) ] or 1 [+0.5 + (+0.5) ] and m = 1 (singlet) or m = 3 (triplet). ''' if nRad in [0, 1]: if multiplicity != (nRad + 1): raise InvalidAdjacencyListError( 'Multiplicity {0} not in agreement with total number of radicals {1}.' .format(multiplicity, nRad)) elif nRad == 2: if not int(multiplicity) in [1, 3]: raise InvalidAdjacencyListError( 'Multiplicity {0} not in agreement with total number of radicals {1}.' .format(multiplicity, nRad)) elif nRad == 3: if not int(multiplicity) in [4, 2]: raise InvalidAdjacencyListError( 'Multiplicity {0} not in agreement with total number of radicals {1}.' .format(multiplicity, nRad)) elif nRad == 4: if not int(multiplicity) in [5, 3, 1]: raise InvalidAdjacencyListError( 'Multiplicity {0} not in agreement with total number of radicals {1}.' .format(multiplicity, nRad)) else: logging.warning( "Consistency checking of multiplicity of molecules with more than 4 unpaired electrons is not implemented yet!" )
def saturate(atoms): ''' Returns a list of atoms that is extended (and bond attributes) by saturating the valency of the non-hydrogen atoms with an appropriate number of hydrogen atoms. The required number of hydrogen atoms per heavy atom is determined as follows: H's = max number of valence electrons - atom.radicalElectrons - 2* atom.lonePairs - order - atom.charge ''' newAtoms = [] for atom in atoms: try: max_number_of_valence_electrons = PeriodicSystem.valence_electrons[atom.symbol] except KeyError: raise InvalidAdjacencyListError('Cannot add hydrogens to adjacency list: Unknown orbital for atom "{0}".'.format(atom.symbol)) order = atom.getBondOrdersForAtom() number_of_H_to_be_added = max_number_of_valence_electrons - atom.radicalElectrons - 2* atom.lonePairs - int(order) - atom.charge if number_of_H_to_be_added < 0: raise InvalidAdjacencyListError('Incorrect electron configuration on atom.') for _ in range(number_of_H_to_be_added): a = Atom(element='H', radicalElectrons=0, charge=0, label='', lonePairs=0) b = Bond(atom, a, 'S') newAtoms.append(a) atom.bonds[a] = b a.bonds[atom] = b atoms.extend(newAtoms)
def check_partial_charge(atom): """ Checks whether the partial charge attribute of the atom checks out with the theoretical one: """ if atom.symbol == 'X': return # because we can't check it. valence = PeriodicSystem.valence_electrons[atom.symbol] order = atom.get_total_bond_order() theoretical = valence - order - atom.radical_electrons - 2 * atom.lone_pairs if not (-0.301 < atom.charge - theoretical < 0.301): # It should be 0, but -0.1 is caused by a Hydrogen bond raise InvalidAdjacencyListError( 'Invalid valency for atom {symbol} ({type}) with {radicals} unpaired electrons, ' '{lone_pairs} pairs of electrons, {charge} charge, and bonds [{bonds}].' .format(symbol=atom.symbol, type=get_atomtype(atom, atom.edges).label, radicals=atom.radical_electrons, lone_pairs=atom.lone_pairs, charge=atom.charge, bonds=','.join([ str(bond.order) for bond in atom.bonds.values() ])))
def check_hund_rule(atom, multiplicity): ''' It is checked whether atoms with 2 unpaired electrons on the same atom result in a multiplicity of 3, and not 1. Unpaired electrons in 2 different orbitals belonging to the same atom should have the same spin, and hence, should result in a multiplicity of 3. ''' if atom.radicalElectrons == 2 and multiplicity == 1: raise InvalidAdjacencyListError("Violation of hund's rule. Invalid multiplicity of {0} because there is an atom with {1} unpaired electrons" .format(multiplicity, atom.radicalElectrons))
def getOldElectronState(atom): """ Get the old adjacency list format electronic state """ additionalLonePairs = atom.lonePairs - PeriodicSystem.lone_pairs[ atom.element.symbol] electrons = atom.radicalElectrons + additionalLonePairs * 2 if electrons == 0: electronState = '0' elif electrons == 1: electronState = '1' elif electrons == 2: if additionalLonePairs == 0: electronState = '2T' elif additionalLonePairs == 1: electronState = '2S' else: raise InvalidAdjacencyListError( "Cannot find electron state of atom {0}".format(atom)) elif electrons == 3: if additionalLonePairs == 0: electronState = '3Q' elif additionalLonePairs == 1: electronState = '3D' else: raise InvalidAdjacencyListError( "Cannot find electron state of atom {0}".format(atom)) elif electrons == 4: if additionalLonePairs == 0: electronState = '4V' elif additionalLonePairs == 1: electronState = '4T' elif additionalLonePairs == 2: electronState = '4S' else: raise InvalidAdjacencyListError( "Cannot find electron state of atom {0}".format(atom)) else: raise InvalidAdjacencyListError( "Cannot find electron state of atom {0}".format(atom)) return electronState
def check_partial_charge(atom): ''' Checks whether the partial charge attribute of the atom checks out with the theoretical one: ''' valence = PeriodicSystem.valence_electrons[atom.symbol] order = atom.getBondOrdersForAtom() theoretical = valence - order - atom.radicalElectrons - 2 * atom.lonePairs if atom.charge != theoretical: raise InvalidAdjacencyListError(( 'Invalid valency for atom {symbol} ({type}) with {radicals} unpaired electrons, ' '{lonePairs} pairs of electrons, {charge} charge, and bonds [{bonds}].' ).format(symbol=atom.symbol, type=getAtomType(atom, atom.edges).label, radicals=atom.radicalElectrons, lonePairs=atom.lonePairs, charge=atom.charge, bonds=','.join( [str(bond.order) for bond in atom.bonds.values()])))
def fromAdjacencyList(adjlist, group=False, saturateH=False): """ Convert a string adjacency list `adjlist` into a set of :class:`Atom` and :class:`Bond` objects. """ atoms = [] atomdict = {} bonds = {} multiplicity = None adjlist = adjlist.strip() lines = adjlist.splitlines() if adjlist == '' or len(lines) == 0: raise InvalidAdjacencyListError('Empty adjacency list.') # Detect old-style adjacency lists by looking at the last line's syntax lastLine = lines[-1].strip() while not lastLine: # Remove any empty lines from the end lines.pop() lastLine = lines[-1].strip() if re_IntermediateAdjList.match(lastLine): logging.debug( "adjacency list:\n{1}\nline '{0}' looks like an intermediate style adjacency list" .format(lastLine, adjlist)) return fromOldAdjacencyList(adjlist, group=group, saturateH=saturateH) if re_OldAdjList.match(lastLine): logging.debug( "Adjacency list:\n{1}\nline '{0}' looks like an old style adjacency list" .format(lastLine, adjlist)) if not group: logging.debug("Will assume implicit H atoms") return fromOldAdjacencyList(adjlist, group=group, saturateH=(not group)) # Interpret the first line if it contains a label if len(lines[0].split()) == 1: label = lines.pop(0) if len(lines) == 0: raise InvalidAdjacencyListError( 'No atoms specified in adjacency list.') # Interpret the second line if it contains a multiplicity if lines[0].split()[0] == 'multiplicity': line = lines.pop(0) if group: match = re.match('\s*multiplicity\s+\[\s*(\d(?:,\s*\d)*)\s*\]\s*$', line) if not match: rematch = re.match('\s*multiplicity\s+x\s*$', line) assert rematch, "Invalid multiplicity line '{0}'. Should be a list like 'multiplicity [1,2,3]' or a wildcard 'multiplicity x'".format( line) else: # should match "multiplicity [1]" or " multiplicity [ 1, 2, 3 ]" or " multiplicity [1,2,3]" # and whatever's inside the [] (excluding leading and trailing spaces) should be captured as group 1. # If a wildcard is desired, this line can be omitted or replaced with 'multiplicity x' # Multiplicities must be only one digit (i.e. less than 10) # The (?:,\s*\d)* matches patters like ", 2" 0 or more times, but doesn't capture them (because of the leading ?:) multiplicities = match.group(1).split(',') multiplicity = [int(i) for i in multiplicities] else: match = re.match('\s*multiplicity\s+\d+\s*$', line) assert match, "Invalid multiplicity line '{0}'. Should be an integer like 'multiplicity 2'".format( line) multiplicity = int(line.split()[1]) if len(lines) == 0: raise InvalidAdjacencyListError( 'No atoms specified in adjacency list: \n{0}'.format(adjlist)) mistake1 = re.compile('\{[^}]*\s+[^}]*\}') # Iterate over the remaining lines, generating Atom or GroupAtom objects for line in lines: # Sometimes people put spaces after commas, which messes up the # parse-by-whitespace. Examples include '[Cd, Ct]'. if mistake1.search(line): raise InvalidAdjacencyListError( "{1} Shouldn't have spaces inside braces:\n{0}".format( mistake1.search(line).group(), adjlist)) # Sometimes commas are used to delimit bonds in the bond list, # so replace them just in case line = line.replace('},{', '} {') data = line.split() # Skip if blank line if len(data) == 0: continue # First item is index for atom # Sometimes these have a trailing period (as if in a numbered list), # so remove it just in case aid = int(data[0].strip('.')) # If second item starts with '*', then atom is labeled label = '' index = 1 if data[1][0] == '*': label = data[1] index += 1 # Next is the element or functional group element # A list can be specified with the {,} syntax atomType = data[index] if atomType[0] == '[': if not group: raise InvalidAdjacencyListError( "Error on:\n{0}\nA molecule should not assign more than one atomtype per atom." .format(adjlist)) atomType = atomType[1:-1].split(',') else: atomType = [atomType] index += 1 # Next the number of unpaired electrons unpairedElectrons = [] uState = data[index] if uState[0] == 'u': if uState[1] == '[': uState = uState[2:-1].split(',') else: uState = [uState[1]] for u in uState: if u == '0': unpairedElectrons.append(0) elif u == '1': unpairedElectrons.append(1) elif u == '2': unpairedElectrons.append(2) elif u == '3': unpairedElectrons.append(3) elif u == '4': unpairedElectrons.append(4) elif u == 'x': if not group: raise InvalidAdjacencyListError( "Error on:\n{0}\nA molecule should not assign a wildcard to number of unpaired electrons." .format(adjlist)) else: raise InvalidAdjacencyListError( 'Number of unpaired electrons not recognized on\n{0}.'. format(adjlist)) index += 1 else: raise InvalidAdjacencyListError( 'Number of unpaired electrons not defined on\n{0}.'.format( adjlist)) # Next the number of lone electron pairs (if provided) lonePairs = [] if len(data) > index: lpState = data[index] if lpState[0] == 'p': if lpState[1] == '[': lpState = lpState[2:-1].split(',') else: lpState = [lpState[1]] for l in lpState: if l == '0': lonePairs.append(0) elif l == '1': lonePairs.append(1) elif l == '2': lonePairs.append(2) elif l == '3': lonePairs.append(3) elif l == '4': lonePairs.append(4) elif l == 'x': if not group: raise InvalidAdjacencyListError( "Error in adjacency list:\n{0}\nA molecule should not have a wildcard assigned to number of lone pairs." .format(adjlist)) else: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{0}\nNumber of lone electron pairs not recognized.' .format(adjlist)) index += 1 else: if not group: lonePairs.append(0) else: if not group: lonePairs.append(0) # Next the number of partial charges (if provided) partialCharges = [] if len(data) > index: eState = data[index] if eState[0] == 'c': if eState[1] == '[': eState = eState[2:-1].split(',') else: eState = [eState[1:]] for e in eState: if e == '0': partialCharges.append(0) elif e == '+1': partialCharges.append(1) elif e == '+2': partialCharges.append(2) elif e == '+3': partialCharges.append(3) elif e == '+4': partialCharges.append(4) elif e == '-1': partialCharges.append(-1) elif e == '-2': partialCharges.append(-2) elif e == '-3': partialCharges.append(-3) elif e == '-4': partialCharges.append(-4) elif e == 'x': if not group: raise InvalidAdjacencyListError( "Error on adjacency list:\n{0}\nA molecule should not have a wildcard assigned to number of charges." .format(adjlist)) else: raise InvalidAdjacencyListError( 'Error on adjacency list:\n{0}\nNumber of partial charges not recognized.' .format(adjlist)) index += 1 else: if not group: partialCharges.append(0) else: if not group: partialCharges.append(0) # Next the isotope (if provided) isotope = -1 if len(data) > index: iState = data[index] if iState[0] == 'i': isotope = int(iState[1:]) index += 1 # Next ring membership info (if provided) props = {} if len(data) > index: rState = data[index] if rState[0] == 'r': props['inRing'] = bool(int(rState[1])) index += 1 # Create a new atom based on the above information if group: atom = GroupAtom(atomType, unpairedElectrons, partialCharges, label, lonePairs, props) else: atom = Atom(atomType[0], unpairedElectrons[0], partialCharges[0], label, lonePairs[0]) if isotope != -1: atom.element = getElement(atom.number, isotope) # Add the atom to the list atoms.append(atom) atomdict[aid] = atom # Process list of bonds bonds[aid] = {} for datum in data[index:]: # Sometimes commas are used to delimit bonds in the bond list, # so strip them just in case datum = datum.strip(',') aid2, comma, order = datum[1:-1].partition(',') aid2 = int(aid2) if aid == aid2: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{1}\nAttempted to create a bond between atom {0:d} and itself.' .format(aid, adjlist)) if order[0] == '[': order = order[1:-1].split(',') else: order = [order] bonds[aid][aid2] = order # Check consistency using bonddict for atom1 in bonds: for atom2 in bonds[atom1]: if atom2 not in bonds: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{1}\nAtom {0:d} not in bond dictionary.' .format(atom2, adjlist)) elif atom1 not in bonds[atom2]: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{2}\nFound bond between {0:d} and {1:d}, but not the reverse.' .format(atom1, atom2, adjlist)) elif bonds[atom1][atom2] != bonds[atom2][atom1]: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{4}\nFound bonds between {0:d} and {1:d}, but of different orders "{2}" and "{3}".' .format(atom1, atom2, bonds[atom1][atom2], bonds[atom2][atom1], adjlist)) # Convert bonddict to use Atom[group] and Bond[group] objects atomkeys = atomdict.keys() atomkeys.sort() for aid1 in atomkeys: atomkeys2 = bonds[aid1].keys() atomkeys2.sort() for aid2 in atomkeys2: if aid1 < aid2: atom1 = atomdict[aid1] atom2 = atomdict[aid2] order = bonds[aid1][aid2] if group: bond = GroupBond(atom1, atom2, order) elif len(order) == 1: bond = Bond(atom1, atom2, order[0]) else: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{0}\nMultiple bond orders specified for an atom in a Molecule.' .format(adjlist)) atom1.edges[atom2] = bond atom2.edges[atom1] = bond if saturateH: # Add explicit hydrogen atoms to complete structure if desired if not group: Saturator.saturate(atoms) # Consistency checks if not group: # Molecule consistency check # Electron and valency consistency check for each atom for atom in atoms: ConsistencyChecker.check_partial_charge(atom) nRad = sum([atom.radicalElectrons for atom in atoms]) absolute_spin_per_electron = 1 / 2. if multiplicity == None: multiplicity = 2 * (nRad * absolute_spin_per_electron) + 1 ConsistencyChecker.check_multiplicity(nRad, multiplicity) for atom in atoms: ConsistencyChecker.check_hund_rule(atom, multiplicity) return atoms, multiplicity else: # Currently no group consistency check return atoms, multiplicity
def fromOldAdjacencyList(adjlist, group=False, saturateH=False): """ Convert a pre-June-2014 string adjacency list `adjlist` into a set of :class:`Atom` and :class:`Bond` objects. It can read both "old style" that existed for years, an the "intermediate style" that existed for a few months in 2014, with the extra column of integers for lone pairs. """ atoms = [] atomdict = {} bonds = {} try: adjlist = adjlist.strip() lines = adjlist.splitlines() if adjlist == '' or len(lines) == 0: raise InvalidAdjacencyListError('Empty adjacency list.') # Skip the first line if it contains a label if len(lines[0].split()) == 1: label = lines.pop(0) if len(lines) == 0: raise InvalidAdjacencyListError( """Error in adjacency list\n{0}\nNo atoms specified.""". format(adjlist)) mistake1 = re.compile('\{[^}]*\s+[^}]*\}') atomicMultiplicities = { } # these are no longer stored on atoms, so we make a separate dictionary # Iterate over the remaining lines, generating Atom or GroupAtom objects for line in lines: # Sometimes people put spaces after commas, which messes up the # parse-by-whitespace. Examples include '{Cd, Ct}'. if mistake1.search(line): raise InvalidAdjacencyListError( "Error in adjacency list: \n{1}\nspecies shouldn't have spaces inside braces: {0}" .format(mistake1.search(line).group(), adjlist)) # Sometimes commas are used to delimit bonds in the bond list, # so replace them just in case line = line.replace('},{', '} {') data = line.split() # Skip if blank line if len(data) == 0: continue # First item is index for atom # Sometimes these have a trailing period (as if in a numbered list), # so remove it just in case aid = int(data[0].strip('.')) # If second item starts with '*', then atom is labeled label = '' index = 1 if data[1][0] == '*': label = data[1] index += 1 # Next is the element or functional group element # A list can be specified with the {,} syntax atomType = data[index] if atomType[0] == '{': atomType = atomType[1:-1].split(',') else: atomType = [atomType] index += 1 # Next is the electron state radicalElectrons = [] additionalLonePairs = [] elecState = data[index].upper() if elecState[0] == '{': elecState = elecState[1:-1].split(',') else: elecState = [elecState] if len(elecState) == 0: raise InvalidAdjacencyListError( "Error in adjacency list:\n{0}\nThere must be some electronic state defined for an old adjlist" .format(adjlist)) for e in elecState: if e == '0': radicalElectrons.append(0) additionalLonePairs.append(0) elif e == '1': radicalElectrons.append(1) additionalLonePairs.append(0) elif e == '2': if not group: raise InvalidAdjacencyListError( "Error in adjacency list:\n{0}\nNumber of radical electrons = 2 is not specific enough. Please use 2S or 2T." .format(adjlist)) # includes 2S and 2T radicalElectrons.append(0) additionalLonePairs.append(1) radicalElectrons.append(2) additionalLonePairs.append(0) elif e == '2S': radicalElectrons.append(0) additionalLonePairs.append(1) elif e == '2T': radicalElectrons.append(2) additionalLonePairs.append(0) elif e == '3': if not group: raise InvalidAdjacencyListError( "Error in adjacency list:\n{0}\nNumber of radical electrons = 3 is not specific enough. Please use 3D or 3Q." .format(adjlist)) # includes 3D and 3Q radicalElectrons.append(1) additionalLonePairs.append(1) radicalElectrons.append(3) additionalLonePairs.append(0) elif e == '3D': radicalElectrons.append(1) additionalLonePairs.append(1) elif e == '3Q': radicalElectrons.append(3) additionalLonePairs.append(0) elif e == '4': if not group: raise InvalidAdjacencyListError( "Error in adjacency list:\n{0}\nNumber of radical electrons = 4 is not specific enough. Please use 4S, 4T, or 4V." .format(adjlist)) # includes 4S, 4T, and 4V radicalElectrons.append(0) additionalLonePairs.append(2) radicalElectrons.append(2) additionalLonePairs.append(1) radicalElectrons.append(4) additionalLonePairs.append(0) elif e == '4S': radicalElectrons.append(0) additionalLonePairs.append(2) elif e == '4T': radicalElectrons.append(2) additionalLonePairs.append(1) elif e == '4V': radicalElectrons.append(4) additionalLonePairs.append(0) elif e == 'X': if not group: raise InvalidAdjacencyListError( "Error in adjacency list:\n{0}\nNumber of radical electrons = X is not specific enough. Wildcards should only be used for groups." .format(adjlist)) radicalElectrons = [] index += 1 # Next number defines the number of lone electron pairs (if provided) lonePairsOfElectrons = None if len(data) > index: lpState = data[index] if lpState[0] == '{': # this is the start of the chemical bonds - no lone pair info was provided lonePairsOfElectrons = None else: if lpState == '0': lonePairsOfElectrons = 0 if lpState == '1': lonePairsOfElectrons = 1 if lpState == '2': lonePairsOfElectrons = 2 if lpState == '3': lonePairsOfElectrons = 3 if lpState == '4': lonePairsOfElectrons = 4 index += 1 else: # no bonds or lone pair info provided. lonePairsOfElectrons = None # Create a new atom based on the above information if group: if lonePairsOfElectrons is not None: lonePairsOfElectrons = [ additional + lonePairsOfElectrons for additional in additionalLonePairs ] atom = GroupAtom( atomType=atomType, radicalElectrons=sorted(set(radicalElectrons)), charge=None, label=label, lonePairs= lonePairsOfElectrons, # Assign lonePairsOfElectrons as None if it is not explicitly provided ) else: if lonePairsOfElectrons is not None: # Intermediate adjlist representation lonePairsOfElectrons = lonePairsOfElectrons + additionalLonePairs[ 0] else: # Add the standard number of lone pairs with the additional lone pairs lonePairsOfElectrons = PeriodicSystem.lone_pairs[ atomType[0]] + additionalLonePairs[0] atom = Atom( element=atomType[0], radicalElectrons=radicalElectrons[0], charge=0, label=label, lonePairs=lonePairsOfElectrons, ) # Add the atom to the list atoms.append(atom) atomdict[aid] = atom # Process list of bonds bonds[aid] = {} for datum in data[index:]: # Sometimes commas are used to delimit bonds in the bond list, # so strip them just in case datum = datum.strip(',') aid2, comma, order = datum[1:-1].partition(',') aid2 = int(aid2) if aid == aid2: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{1}\nAttempted to create a bond between atom {0:d} and itself.' .format(aid, adjlist)) if order[0] == '{': order = order[1:-1].split(',') else: order = [order] bonds[aid][aid2] = order # Check consistency using bonddict for atom1 in bonds: for atom2 in bonds[atom1]: if atom2 not in bonds: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{1}\nAtom {0:d} not in bond dictionary.' .format(atom2, adjlist)) elif atom1 not in bonds[atom2]: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{2}\nFound bond between {0:d} and {1:d}, but not the reverse' .format(atom1, atom2, adjlist)) elif bonds[atom1][atom2] != bonds[atom2][atom1]: raise InvalidAdjacencyListError( 'Error in adjacency list: \n{4}\nFound bonds between {0:d} and {1:d}, but of different orders "{2}" and "{3}".' .format(atom1, atom2, bonds[atom1][atom2], bonds[atom2][atom1], adjlist)) # Convert bonddict to use Atom[group] and Bond[group] objects atomkeys = atomdict.keys() atomkeys.sort() for aid1 in atomkeys: atomkeys2 = bonds[aid1].keys() atomkeys2.sort() for aid2 in atomkeys2: if aid1 < aid2: atom1 = atomdict[aid1] atom2 = atomdict[aid2] order = bonds[aid1][aid2] if group: bond = GroupBond(atom1, atom2, order) elif len(order) == 1: bond = Bond(atom1, atom2, order[0]) else: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{0}\nMultiple bond orders specified for an atom.' .format(adjlist)) atom1.edges[atom2] = bond atom2.edges[atom1] = bond if not group: if saturateH: # Add explicit hydrogen atoms to complete structure if desired newAtoms = [] for atom in atoms: try: valence = PeriodicSystem.valences[atom.symbol] except KeyError: raise InvalidAdjacencyListError( 'Error in adjacency list:\n{1}\nCannot add hydrogens: Unknown valence for atom "{0}".' .format(atom.symbol, adjlist)) radical = atom.radicalElectrons order = atom.getBondOrdersForAtom() count = valence - radical - int( order) - 2 * (atom.lonePairs - PeriodicSystem.lone_pairs[atom.symbol]) for i in range(count): a = Atom(element='H', radicalElectrons=0, charge=0, label='', lonePairs=0) b = Bond(atom, a, 'S') newAtoms.append(a) atom.bonds[a] = b a.bonds[atom] = b atoms.extend(newAtoms) # Calculate the multiplicity for the molecule and update the charges on each atom nRad = 0 # total number of radical electrons for atom in atoms: atom.updateCharge() nRad += atom.radicalElectrons multiplicity = nRad + 1 # 2 s + 1, where s is the combined spin of unpaired electrons (s = 1/2 per unpaired electron) else: # Don't set a multiplicity for groups when converting from an old adjlist multiplicity = None except InvalidAdjacencyListError: logging.error("Troublesome adjacency list:\n" + adjlist) raise return atoms, multiplicity
def toOldAdjacencyList(atoms, multiplicity=None, label=None, group=False, removeH=False): """ Convert a chemical graph defined by a list of `atoms` into a string old-style adjacency list that can be used in RMG-Java. Currently not working for groups. """ adjlist = '' if group: raise InvalidAdjacencyListError("Not yet implemented.") # Filter out all non-valid atoms if not group: for atom in atoms: if atom.element.symbol in ['He', 'Ne', 'Ar', 'N']: raise InvalidAdjacencyListError( "Old-style adjacency list does not accept He, Ne, Ar, N elements." ) # Don't remove hydrogen atoms if the molecule consists only of hydrogen atoms try: if removeH and all([atom.element.symbol == 'H' for atom in atoms]): removeH = False except AttributeError: pass if label: adjlist += label + '\n' # Determine the numbers to use for each atom atomNumbers = {} index = 0 for atom in atoms: if removeH and atom.element.symbol == 'H' and atom.label == '': continue atomNumbers[atom] = '{0:d}'.format(index + 1) index += 1 atomLabels = dict([(atom, '{0}'.format(atom.label)) for atom in atomNumbers]) atomTypes = {} atomElectronStates = {} if group: raise InvalidAdjacencyListError("Not yet implemented.") else: for atom in atomNumbers: # Atom type atomTypes[atom] = '{0}'.format(atom.element.symbol) # Electron state(s) atomElectronStates[atom] = '{0}'.format(getOldElectronState(atom)) # Determine field widths atomNumberWidth = max([len(s) for s in atomNumbers.values()]) + 1 atomLabelWidth = max([len(s) for s in atomLabels.values()]) if atomLabelWidth > 0: atomLabelWidth += 1 atomTypeWidth = max([len(s) for s in atomTypes.values()]) + 1 atomElectronStateWidth = max([len(s) for s in atomElectronStates.values()]) # Assemble the adjacency list for atom in atoms: if atom not in atomNumbers: continue # Atom number adjlist += '{0:<{1:d}}'.format(atomNumbers[atom], atomNumberWidth) # Atom label adjlist += '{0:<{1:d}}'.format(atomLabels[atom], atomLabelWidth) # Atom type(s) adjlist += '{0:<{1:d}}'.format(atomTypes[atom], atomTypeWidth) # Electron state(s) adjlist += '{0:<{1:d}}'.format(atomElectronStates[atom], atomElectronStateWidth) # Bonds list atoms2 = atom.bonds.keys() # sort them the same way as the atoms atoms2.sort(key=atoms.index) for atom2 in atoms2: if atom2 not in atomNumbers: continue bond = atom.bonds[atom2] adjlist += ' {{{0},'.format(atomNumbers[atom2]) # Bond type(s) if group: if len(bond.order) == 1: adjlist += bond.getOrderStr()[0] else: adjlist += '{{{0}}}'.format(','.join(bond.getOrderStr())) else: adjlist += bond.getOrderStr() adjlist += '}' # Each atom begins on a new line adjlist += '\n' return adjlist