Skip to content

Commit 80e613a

Browse files
rgillis8alongd
authored andcommitted
Adds resonance for radicals adjacent to sulfur lone pairs
1 parent 8a027f6 commit 80e613a

File tree

4 files changed

+138
-11
lines changed

4 files changed

+138
-11
lines changed

rmgpy/molecule/pathfinder.pxd

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ cpdef dict compute_atom_distance(list atom_indices, Molecule mol)
1919

2020
cpdef list findAllDelocalizationPaths(Atom atom1)
2121

22-
cpdef list findAllDelocalizationPathsLonePairRadical(Atom atom1)
22+
cpdef list findAllDelocalizationPathsLonePairRadicalCharge(Atom atom1)
23+
24+
cpdef list findAllDelocalizationPathsLonePairRadicalDoubleBond(Atom atom1)
2325

2426
cpdef list findAllDelocalizationPathsN5dd_N5ts(Atom atom1)

rmgpy/molecule/pathfinder.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ def findAllDelocalizationPaths(atom1):
260260
paths.append([atom1, atom2, atom3, bond12, bond23])
261261
return paths
262262

263-
def findAllDelocalizationPathsLonePairRadical(atom1):
263+
def findAllDelocalizationPathsLonePairRadicalCharge(atom1):
264264
"""
265265
Find all the delocalization paths of lone electron pairs next to the radical center indicated
266266
by `atom1`. Used to generate resonance isomers.
@@ -271,9 +271,8 @@ def findAllDelocalizationPathsLonePairRadical(atom1):
271271
# No paths if atom1 is not a radical
272272
if atom1.radicalElectrons <= 0:
273273
return []
274-
275-
# In a first step we only consider nitrogen and oxygen atoms as possible radical centers
276-
if not ((atom1.lonePairs == 0 and atom1.isNitrogen()) or(atom1.lonePairs == 2 and atom1.isOxygen())):
274+
# In a first step we only consider nitrogen, sulfur and oxygen atoms as possible radical centers
275+
if not ((atom1.lonePairs == 0 and atom1.isNitrogen()) or(atom1.lonePairs == 2 and atom1.isOxygen()) or (atom1.lonePairs <= 1 and atom1.isSulfur())):
277276
return []
278277

279278
# Find all delocalization paths
@@ -282,11 +281,50 @@ def findAllDelocalizationPathsLonePairRadical(atom1):
282281
# Only single bonds are considered
283282
if bond12.isSingle():
284283
# Neighboring atom must posses a lone electron pair to loose it
285-
if ((atom2.lonePairs == 1 and atom2.isNitrogen()) or (atom2.lonePairs == 3 and atom2.isOxygen())) and (atom2.radicalElectrons == 0):
284+
if ((atom2.lonePairs == 1 and atom2.isNitrogen()) or (atom2.lonePairs == 3 and atom2.isOxygen()) or (atom2.lonePairs == 2 and atom2.isSulfur())) and (atom2.radicalElectrons == 0):
286285
paths.append([atom1, atom2])
287286

288287
return paths
289288

289+
def findAllDelocalizationPathsLonePairRadicalDoubleBond(atom1):
290+
"""
291+
Find all the delocalization paths of lone electron pairs next to the radical center indicated
292+
by `atom1`. Used to generate resonance isomers.
293+
"""
294+
cython.declare(paths=list)
295+
cython.declare(atom2=Atom, bond12=Bond)
296+
297+
# No paths if atom1 is not a radical
298+
if atom1.radicalElectrons <= 0:
299+
return []
300+
if atom1.charge != 0:
301+
return []
302+
303+
# Find all delocalization paths
304+
#radical splits a lone pair on a sulfur atom
305+
paths = []
306+
for atom2, bond12 in atom1.edges.items():
307+
if atom2.charge != 0:
308+
continue
309+
# Only single bonds are considered
310+
if bond12.isSingle():
311+
if (atom2.lonePairs >= 1 and atom2.isSulfur()) and (atom2.radicalElectrons == 0):
312+
paths.append([atom1, atom2, bond12, 1])
313+
314+
#sulfur radical resonates to form a lone pair and radical on the adjacent atom
315+
#only consider sulfur radicals that have less than 2 lone pairs (i.e. can form another lone pair)
316+
if not (atom1.lonePairs <= 1 and atom1.isSulfur()):
317+
return paths
318+
319+
#cycle through adjacent atoms until you find one that is not a radical itself and connected to atom1 by a double or triple bond
320+
for atom2, bond12 in atom1.edges.items():
321+
if atom2.charge != 0:
322+
continue
323+
if bond12.isDouble() or bond12.isTriple():
324+
if atom2.radicalElectrons == 0:
325+
paths.append([atom1, atom2, bond12, 2])
326+
return paths
327+
290328
def findAllDelocalizationPathsN5dd_N5ts(atom1):
291329
"""
292330
Find all the resonance structures of nitrogen atoms with two double bonds (N5dd)

rmgpy/molecule/resonance.pxd

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ cpdef list _generateResonanceStructures(list molList, list methodList, bint keep
1111

1212
cpdef list generateAdjacentResonanceStructures(Molecule mol)
1313

14-
cpdef list generateLonePairRadicalResonanceStructures(Molecule mol)
14+
cpdef list generateLonePairRadicalResonanceChargeStructures(Molecule mol)
15+
16+
cpdef list generateLonePairRadicalResonanceDoubleBondStructures(Molecule mol)
1517

1618
cpdef list generateN5dd_N5tsResonanceStructures(Molecule mol)
1719

rmgpy/molecule/resonance.py

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ def populateResonanceAlgorithms(features=None):
7171
if features is None:
7272
methodList = [
7373
generateAdjacentResonanceStructures,
74-
generateLonePairRadicalResonanceStructures,
74+
generateLonePairRadicalResonanceChargeStructures,
75+
generateLonePairRadicalResonanceDoubleBondStructures,
7576
generateN5dd_N5tsResonanceStructures,
7677
generateAromaticResonanceStructures,
7778
generateKekuleStructure,
@@ -87,7 +88,9 @@ def populateResonanceAlgorithms(features=None):
8788
if features['hasNitrogen']:
8889
methodList.append(generateN5dd_N5tsResonanceStructures)
8990
if features['hasLonePairs']:
90-
methodList.append(generateLonePairRadicalResonanceStructures)
91+
methodList.append(generateLonePairRadicalResonanceChargeStructures)
92+
if features['hasLonePairs']:
93+
methodList.append(generateLonePairRadicalResonanceDoubleBondStructures)
9194

9295
return methodList
9396

@@ -106,6 +109,7 @@ def analyzeMolecule(mol):
106109
'isArylRadical': False,
107110
'hasNitrogen': False,
108111
'hasOxygen': False,
112+
'hasSulfur': False,
109113
'hasLonePairs': False,
110114
}
111115

@@ -122,6 +126,8 @@ def analyzeMolecule(mol):
122126
features['hasNitrogen'] = True
123127
if atom.isOxygen():
124128
features['hasOxygen'] = True
129+
if atom.isSulfur():
130+
features['hasSulfur'] = True
125131
if atom.lonePairs > 0:
126132
features['hasLonePairs'] = True
127133

@@ -301,7 +307,7 @@ def generateAdjacentResonanceStructures(mol):
301307

302308
return isomers
303309

304-
def generateLonePairRadicalResonanceStructures(mol):
310+
def generateLonePairRadicalResonanceChargeStructures(mol):
305311
"""
306312
Generate all of the resonance structures formed by lone electron pair - radical shifts.
307313
"""
@@ -315,7 +321,7 @@ def generateLonePairRadicalResonanceStructures(mol):
315321
if mol.isRadical():
316322
# Iterate over radicals in structure
317323
for atom in mol.vertices:
318-
paths = pathfinder.findAllDelocalizationPathsLonePairRadical(atom)
324+
paths = pathfinder.findAllDelocalizationPathsLonePairRadicalCharge(atom)
319325
for atom1, atom2 in paths:
320326
# Adjust to (potentially) new resonance isomer
321327
atom1.decrementRadical()
@@ -348,6 +354,85 @@ def generateLonePairRadicalResonanceStructures(mol):
348354

349355
return isomers
350356

357+
def generateLonePairRadicalResonanceDoubleBondStructures(mol):
358+
"""
359+
Generate all of the resonance structures formed by lone electron pair - radical shifts.
360+
"""
361+
cython.declare(isomers=list, paths=list, index=cython.int, isomer=Molecule)
362+
cython.declare(atom=Atom, atom1=Atom, atom2=Atom)
363+
cython.declare(v1=Vertex, v2=Vertex)
364+
365+
isomers = []
366+
367+
# Radicals
368+
if mol.isRadical():
369+
# Iterate over radicals in structure
370+
for atom in mol.vertices:
371+
paths = pathfinder.findAllDelocalizationPathsLonePairRadicalDoubleBond(atom)
372+
for atom1, atom2, bond12, direction in paths:
373+
#a radical adjacent to a sulfur atom forms a higher order bond and moves the radical electron to the sulfur
374+
if direction == 1:
375+
# Adjust to (potentially) new resonance isomer
376+
atom1.decrementRadical()
377+
atom2.incrementRadical()
378+
atom2.decrementLonePairs()
379+
bond12.incrementOrder()
380+
atom1.updateCharge()
381+
atom2.updateCharge()
382+
# Make a copy of isomer
383+
isomer = mol.copy(deep=True)
384+
# Also copy the connectivity values, since they are the same
385+
# for all resonance forms
386+
for index in range(len(mol.vertices)):
387+
v1 = mol.vertices[index]
388+
v2 = isomer.vertices[index]
389+
v2.connectivity1 = v1.connectivity1
390+
v2.connectivity2 = v1.connectivity2
391+
v2.connectivity3 = v1.connectivity3
392+
v2.sortingLabel = v1.sortingLabel
393+
# Restore current isomer
394+
atom1.incrementRadical()
395+
atom2.decrementRadical()
396+
atom2.incrementLonePairs()
397+
bond12.decrementOrder()
398+
atom1.updateCharge()
399+
atom2.updateCharge()
400+
# Append to isomer list if unique
401+
isomer.updateAtomTypes(logSpecies=False)
402+
isomers.append(isomer)
403+
#now adsorb an electron from an adjacent double or triple bond to a sulfur into a lone pair
404+
elif direction == 2:
405+
# Adjust to (potentially) new resonance isomer
406+
atom1.decrementRadical()
407+
atom2.incrementRadical()
408+
atom1.incrementLonePairs()
409+
bond12.decrementOrder()
410+
atom1.updateCharge()
411+
atom2.updateCharge()
412+
# Make a copy of isomer
413+
isomer = mol.copy(deep=True)
414+
# Also copy the connectivity values, since they are the same
415+
# for all resonance forms
416+
for index in range(len(mol.vertices)):
417+
v1 = mol.vertices[index]
418+
v2 = isomer.vertices[index]
419+
v2.connectivity1 = v1.connectivity1
420+
v2.connectivity2 = v1.connectivity2
421+
v2.connectivity3 = v1.connectivity3
422+
v2.sortingLabel = v1.sortingLabel
423+
# Restore current isomer
424+
atom1.incrementRadical()
425+
atom2.decrementRadical()
426+
atom1.decrementLonePairs()
427+
bond12.incrementOrder()
428+
atom1.updateCharge()
429+
atom2.updateCharge()
430+
# Append to isomer list if unique
431+
isomer.updateAtomTypes(logSpecies=False)
432+
isomers.append(isomer)
433+
return isomers
434+
435+
351436
def generateN5dd_N5tsResonanceStructures(mol):
352437
"""
353438
Generate all of the resonance structures formed by shifts between N5dd and N5ts.

0 commit comments

Comments
 (0)