firecode 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- firecode/TEST_NOTEBOOK.ipynb +3940 -0
- firecode/__init__.py +0 -0
- firecode/__main__.py +118 -0
- firecode/_gaussian.py +97 -0
- firecode/algebra.py +405 -0
- firecode/ase_manipulations.py +879 -0
- firecode/atropisomer_module.py +516 -0
- firecode/automep.py +130 -0
- firecode/calculators/__init__.py +29 -0
- firecode/calculators/_gaussian.py +98 -0
- firecode/calculators/_mopac.py +242 -0
- firecode/calculators/_openbabel.py +154 -0
- firecode/calculators/_orca.py +129 -0
- firecode/calculators/_xtb.py +786 -0
- firecode/concurrent_test.py +119 -0
- firecode/embedder.py +2590 -0
- firecode/embedder_options.py +577 -0
- firecode/embeds.py +881 -0
- firecode/errors.py +65 -0
- firecode/graph_manipulations.py +333 -0
- firecode/hypermolecule_class.py +364 -0
- firecode/mep_relaxer.py +199 -0
- firecode/modify_settings.py +186 -0
- firecode/mprof.py +65 -0
- firecode/multiembed.py +148 -0
- firecode/nci.py +186 -0
- firecode/numba_functions.py +260 -0
- firecode/operators.py +776 -0
- firecode/optimization_methods.py +609 -0
- firecode/parameters.py +84 -0
- firecode/pka.py +275 -0
- firecode/profiler.py +17 -0
- firecode/pruning.py +421 -0
- firecode/pt.py +32 -0
- firecode/quotes.json +6651 -0
- firecode/quotes.py +9 -0
- firecode/reactive_atoms_classes.py +666 -0
- firecode/references.py +11 -0
- firecode/rmsd.py +74 -0
- firecode/settings.py +75 -0
- firecode/solvents.py +126 -0
- firecode/tests/C2F2H4.xyz +10 -0
- firecode/tests/C2H4.xyz +8 -0
- firecode/tests/CH3Cl.xyz +7 -0
- firecode/tests/HCOOH.xyz +7 -0
- firecode/tests/HCOOOH.xyz +8 -0
- firecode/tests/chelotropic.txt +3 -0
- firecode/tests/cyclical.txt +3 -0
- firecode/tests/dihedral.txt +2 -0
- firecode/tests/string.txt +3 -0
- firecode/tests/trimolecular.txt +9 -0
- firecode/tests.py +151 -0
- firecode/torsion_module.py +1035 -0
- firecode/utils.py +541 -0
- firecode-1.0.0.dist-info/LICENSE +165 -0
- firecode-1.0.0.dist-info/METADATA +321 -0
- firecode-1.0.0.dist-info/RECORD +59 -0
- firecode-1.0.0.dist-info/WHEEL +5 -0
- firecode-1.0.0.dist-info/top_level.txt +1 -0
firecode/quotes.py
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
'''
|
|
3
|
+
FIRECODE: Filtering Refiner and Embedder for Conformationally Dense Ensembles
|
|
4
|
+
Copyright (C) 2021-2024 Nicolò Tampellini
|
|
5
|
+
|
|
6
|
+
SPDX-License-Identifier: LGPL-3.0-or-later
|
|
7
|
+
|
|
8
|
+
This program is free software: you can redistribute it and/or modify
|
|
9
|
+
it under the terms of the GNU Lesser General Public License as published by
|
|
10
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
(at your option) any later version.
|
|
12
|
+
|
|
13
|
+
This program is distributed in the hope that it will be useful,
|
|
14
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
GNU Lesser General Public License for more details.
|
|
17
|
+
|
|
18
|
+
You should have received a copy of the GNU Lesser General Public License
|
|
19
|
+
along with this program. If not, see
|
|
20
|
+
https://www.gnu.org/licenses/lgpl-3.0.en.html#license-text.
|
|
21
|
+
|
|
22
|
+
'''
|
|
23
|
+
|
|
24
|
+
from copy import deepcopy
|
|
25
|
+
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
from firecode.algebra import norm, norm_of, rot_mat_from_pointer, vec_angle
|
|
29
|
+
from firecode.graph_manipulations import neighbors
|
|
30
|
+
from firecode.parameters import orb_dim_dict
|
|
31
|
+
from firecode.pt import pt
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Single:
|
|
35
|
+
|
|
36
|
+
def __repr__(self):
|
|
37
|
+
return 'Single Bond'
|
|
38
|
+
|
|
39
|
+
def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
|
|
40
|
+
'''
|
|
41
|
+
'''
|
|
42
|
+
self.index = i
|
|
43
|
+
self.symbol = pt[mol.atomnos[i]].symbol
|
|
44
|
+
neighbors_indices = neighbors(mol.graph, i)
|
|
45
|
+
|
|
46
|
+
self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indices]
|
|
47
|
+
self.coord = mol.atomcoords[conf][i]
|
|
48
|
+
self.other = mol.atomcoords[conf][neighbors_indices][0]
|
|
49
|
+
|
|
50
|
+
if not mol.sp3_sigmastar:
|
|
51
|
+
self.orb_vecs = np.array([norm(self.coord - self.other)])
|
|
52
|
+
|
|
53
|
+
else:
|
|
54
|
+
other_reactive_indices = list(mol.reactive_indices)
|
|
55
|
+
other_reactive_indices.remove(i)
|
|
56
|
+
for index in other_reactive_indices:
|
|
57
|
+
if index in neighbors_indices:
|
|
58
|
+
parnter_index = index
|
|
59
|
+
break
|
|
60
|
+
# obtain the reference partner index
|
|
61
|
+
|
|
62
|
+
partner = mol.atomcoords[conf][parnter_index]
|
|
63
|
+
pivot = norm(partner - self.coord)
|
|
64
|
+
|
|
65
|
+
neighbors_of_partner = neighbors(mol.graph, parnter_index)
|
|
66
|
+
neighbors_of_partner.remove(i)
|
|
67
|
+
orb_vec = norm(mol.atomcoords[conf][neighbors_of_partner[0]] - partner)
|
|
68
|
+
orb_vec = orb_vec - orb_vec @ pivot * pivot
|
|
69
|
+
|
|
70
|
+
steps = 3 # number of total orbitals
|
|
71
|
+
self.orb_vecs = np.array([rot_mat_from_pointer(pivot, angle+60) @ orb_vec for angle in range(0,360,int(360/steps))])
|
|
72
|
+
# orbitals are staggered in relation to sp3 substituents
|
|
73
|
+
|
|
74
|
+
self.orb_vers = norm(self.orb_vecs[0])
|
|
75
|
+
|
|
76
|
+
if update:
|
|
77
|
+
if orb_dim is None:
|
|
78
|
+
key = self.symbol + ' ' + str(self).split(' (')[0]
|
|
79
|
+
orb_dim = orb_dim_dict.get(key)
|
|
80
|
+
|
|
81
|
+
if orb_dim is None:
|
|
82
|
+
orb_dim = norm_of(self.coord - self.other)
|
|
83
|
+
print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using the bonding distance ({round(orb_dim, 3)} A).')
|
|
84
|
+
|
|
85
|
+
self.center = orb_dim * self.orb_vecs + self.coord
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class Sp2:
|
|
89
|
+
|
|
90
|
+
def __repr__(self):
|
|
91
|
+
return 'sp2'
|
|
92
|
+
|
|
93
|
+
def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
|
|
94
|
+
'''
|
|
95
|
+
'''
|
|
96
|
+
self.index = i
|
|
97
|
+
self.symbol = pt[mol.atomnos[i]].symbol
|
|
98
|
+
neighbors_indices = neighbors(mol.graph, i)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indices]
|
|
103
|
+
self.coord = mol.atomcoords[conf][i]
|
|
104
|
+
self.others = mol.atomcoords[conf][neighbors_indices]
|
|
105
|
+
|
|
106
|
+
self.vectors = self.others - self.coord # vectors connecting reactive atom with neighbors
|
|
107
|
+
self.orb_vec = norm(np.mean(np.array([np.cross(norm(self.vectors[0]), norm(self.vectors[1])),
|
|
108
|
+
np.cross(norm(self.vectors[1]), norm(self.vectors[2])),
|
|
109
|
+
np.cross(norm(self.vectors[2]), norm(self.vectors[0]))]), axis=0))
|
|
110
|
+
|
|
111
|
+
self.orb_vecs = np.vstack((self.orb_vec, -self.orb_vec))
|
|
112
|
+
|
|
113
|
+
if update:
|
|
114
|
+
if orb_dim is None:
|
|
115
|
+
key = self.symbol + ' ' + str(self).split(' (')[0]
|
|
116
|
+
orb_dim = orb_dim_dict.get(key)
|
|
117
|
+
|
|
118
|
+
if orb_dim is None:
|
|
119
|
+
orb_dim = orb_dim_dict['Fallback']
|
|
120
|
+
print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using {orb_dim} A.')
|
|
121
|
+
|
|
122
|
+
self.center = self.orb_vecs * orb_dim
|
|
123
|
+
|
|
124
|
+
self.center += self.coord
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class Sp3:
|
|
128
|
+
|
|
129
|
+
def __repr__(self):
|
|
130
|
+
return 'sp3'
|
|
131
|
+
|
|
132
|
+
def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
|
|
133
|
+
|
|
134
|
+
self.index = i
|
|
135
|
+
self.symbol = pt[mol.atomnos[i]].symbol
|
|
136
|
+
neighbors_indices = neighbors(mol.graph, i)
|
|
137
|
+
self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indices]
|
|
138
|
+
self.coord = mol.atomcoords[conf][i]
|
|
139
|
+
self.others = mol.atomcoords[conf][neighbors_indices]
|
|
140
|
+
|
|
141
|
+
if not mol.sp3_sigmastar:
|
|
142
|
+
|
|
143
|
+
if not hasattr(self, 'leaving_group_index'):
|
|
144
|
+
self.leaving_group_index = None
|
|
145
|
+
|
|
146
|
+
if len([atom for atom in self.neighbors_symbols if atom in ['O', 'N', 'Cl', 'Br', 'I']]) == 1: # if we can tell where is the leaving group
|
|
147
|
+
self.leaving_group_coords = self.others[self.neighbors_symbols.index([atom for atom in self.neighbors_symbols if atom in ['O', 'Cl', 'Br', 'I']][0])]
|
|
148
|
+
|
|
149
|
+
elif len([atom for atom in self.neighbors_symbols if atom not in ['H']]) == 1: # if no clear leaving group but we only have one atom != H
|
|
150
|
+
self.leaving_group_coords = self.others[self.neighbors_symbols.index([atom for atom in self.neighbors_symbols if atom not in ['H']][0])]
|
|
151
|
+
|
|
152
|
+
else: # if we cannot infer, ask user if we didn't have already
|
|
153
|
+
try:
|
|
154
|
+
self.leaving_group_coords = self._set_leaving_group(mol, neighbors_indices)
|
|
155
|
+
|
|
156
|
+
except Exception:
|
|
157
|
+
# if something goes wrong, we fallback to command line input for reactive atom index collection
|
|
158
|
+
|
|
159
|
+
if self.leaving_group_index is None:
|
|
160
|
+
|
|
161
|
+
while True:
|
|
162
|
+
|
|
163
|
+
self.leaving_group_index = input(f'Please insert the index of the leaving group atom bonded to the sp3 reactive atom (index {self.index}) of molecule {mol.rootname} : ')
|
|
164
|
+
|
|
165
|
+
if self.leaving_group_index == '' or self.leaving_group_index.lower().islower():
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
elif int(self.leaving_group_index) in neighbors_indices:
|
|
169
|
+
self.leaving_group_index = int(self.leaving_group_index)
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
else:
|
|
173
|
+
print(f'Atom {self.leaving_group_index} is not bonded to the sp3 center with index {self.index}.')
|
|
174
|
+
|
|
175
|
+
self.leaving_group_coords = self.others[neighbors_indices.index(self.leaving_group_index)]
|
|
176
|
+
|
|
177
|
+
self.orb_vecs = np.array([self.coord - self.leaving_group_coords])
|
|
178
|
+
self.orb_vers = norm(self.orb_vecs[0])
|
|
179
|
+
|
|
180
|
+
else: # Sigma bond type
|
|
181
|
+
|
|
182
|
+
other_reactive_indices = list(mol.reactive_indices)
|
|
183
|
+
other_reactive_indices.remove(i)
|
|
184
|
+
for index in other_reactive_indices:
|
|
185
|
+
if index in neighbors_indices:
|
|
186
|
+
parnter_index = index
|
|
187
|
+
break
|
|
188
|
+
# obtain the reference partner index
|
|
189
|
+
|
|
190
|
+
pivot = norm(mol.atomcoords[conf][parnter_index] - self.coord)
|
|
191
|
+
|
|
192
|
+
other_neighbors = deepcopy(neighbors_indices)
|
|
193
|
+
other_neighbors.remove(parnter_index)
|
|
194
|
+
orb_vec = norm(mol.atomcoords[conf][other_neighbors[0]] - self.coord)
|
|
195
|
+
orb_vec = orb_vec - orb_vec @ pivot * pivot
|
|
196
|
+
|
|
197
|
+
steps = 3 # number of total orbitals
|
|
198
|
+
self.orb_vecs = np.array([rot_mat_from_pointer(pivot, angle+60) @ orb_vec for angle in range(0,360,int(360/steps))])
|
|
199
|
+
# orbitals are staggered in relation to sp3 substituents
|
|
200
|
+
|
|
201
|
+
self.orb_vers = norm(self.orb_vecs[0])
|
|
202
|
+
|
|
203
|
+
if update:
|
|
204
|
+
if orb_dim is None:
|
|
205
|
+
key = self.symbol + ' ' + str(self).split(' (')[0]
|
|
206
|
+
orb_dim = orb_dim_dict.get(key)
|
|
207
|
+
|
|
208
|
+
if orb_dim is None:
|
|
209
|
+
orb_dim = orb_dim_dict['Fallback']
|
|
210
|
+
print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using {orb_dim} A.')
|
|
211
|
+
|
|
212
|
+
self.center = np.array([orb_dim * norm(vec) + self.coord for vec in self.orb_vecs])
|
|
213
|
+
|
|
214
|
+
def _set_leaving_group(self, mol, neighbors_indices):
|
|
215
|
+
'''
|
|
216
|
+
Manually set the molecule leaving group from the ASE GUI, imposing
|
|
217
|
+
a constraint on the desired atom.
|
|
218
|
+
|
|
219
|
+
'''
|
|
220
|
+
|
|
221
|
+
if self.leaving_group_index is None:
|
|
222
|
+
|
|
223
|
+
from ase import Atoms
|
|
224
|
+
from ase.gui.gui import GUI
|
|
225
|
+
from ase.gui.images import Images
|
|
226
|
+
|
|
227
|
+
atoms = Atoms(mol.atomnos, positions=mol.atomcoords[0])
|
|
228
|
+
|
|
229
|
+
while True:
|
|
230
|
+
print(('\nPlease, manually select the leaving group atom for molecule %s'
|
|
231
|
+
'\nbonded to the sp3 reactive atom with index %s.'
|
|
232
|
+
'\nRotate with right click and select atoms by clicking.'
|
|
233
|
+
'\nThen go to Tools -> Constraints -> Constrain, and close the GUI.') % (mol.filename, self.index))
|
|
234
|
+
|
|
235
|
+
GUI(images=Images([atoms]), show_bonds=True).run()
|
|
236
|
+
|
|
237
|
+
if atoms.constraints != []:
|
|
238
|
+
if len(list(atoms.constraints[0].get_indices())) == 1:
|
|
239
|
+
if list(atoms.constraints[0].get_indices())[0] in neighbors_indices:
|
|
240
|
+
self.leaving_group_index = list(atoms.constraints[0].get_indices())[0]
|
|
241
|
+
break
|
|
242
|
+
else:
|
|
243
|
+
print('\nSeems that the atom you selected is not bonded to the reactive center or is the reactive atom itself.\nThis is probably an error, please try again.')
|
|
244
|
+
atoms.constraints = []
|
|
245
|
+
else:
|
|
246
|
+
print('\nPlease only select one leaving group atom.')
|
|
247
|
+
atoms.constraints = []
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
return self.others[neighbors_indices.index(self.leaving_group_index)]
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class Ether:
|
|
254
|
+
|
|
255
|
+
def __repr__(self):
|
|
256
|
+
return 'Ether'
|
|
257
|
+
|
|
258
|
+
def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
|
|
259
|
+
'''
|
|
260
|
+
'''
|
|
261
|
+
self.index = i
|
|
262
|
+
self.symbol = pt[mol.atomnos[i]].symbol
|
|
263
|
+
neighbors_indices = neighbors(mol.graph, i)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indices]
|
|
268
|
+
self.coord = mol.atomcoords[conf][i]
|
|
269
|
+
self.others = mol.atomcoords[conf][neighbors_indices]
|
|
270
|
+
|
|
271
|
+
self.orb_vecs = self.others - self.coord # vectors connecting center to each of the two substituents
|
|
272
|
+
|
|
273
|
+
if update:
|
|
274
|
+
if orb_dim is None:
|
|
275
|
+
key = self.symbol + ' ' + str(self).split(' (')[0]
|
|
276
|
+
orb_dim = orb_dim_dict.get(key)
|
|
277
|
+
|
|
278
|
+
if orb_dim is None:
|
|
279
|
+
orb_dim = orb_dim_dict['Fallback']
|
|
280
|
+
print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using {orb_dim} A.')
|
|
281
|
+
|
|
282
|
+
self.orb_vecs = orb_dim * np.array([norm(v) for v in self.orb_vecs]) # making both vectors a fixed, defined length
|
|
283
|
+
|
|
284
|
+
orb_mat = rot_mat_from_pointer(np.mean(self.orb_vecs, axis=0), 90) @ rot_mat_from_pointer(np.cross(self.orb_vecs[0], self.orb_vecs[1]), 180)
|
|
285
|
+
|
|
286
|
+
# self.orb_vecs = np.array([orb_mat @ v for v in self.orb_vecs])
|
|
287
|
+
self.orb_vecs = (orb_mat @ self.orb_vecs.T).T
|
|
288
|
+
|
|
289
|
+
self.center = self.orb_vecs + self.coord
|
|
290
|
+
# two vectors defining the position of the two orbital lobes centers
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class Ketone:
|
|
294
|
+
|
|
295
|
+
def __repr__(self):
|
|
296
|
+
return f'Ketone ({self.subtype})'
|
|
297
|
+
|
|
298
|
+
def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
|
|
299
|
+
'''
|
|
300
|
+
'''
|
|
301
|
+
self.index = i
|
|
302
|
+
self.symbol = pt[mol.atomnos[i]].symbol
|
|
303
|
+
neighbors_indices = neighbors(mol.graph, i)
|
|
304
|
+
self.subtype = 'pre-init'
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indices]
|
|
308
|
+
self.coord = mol.atomcoords[conf][i]
|
|
309
|
+
self.other = mol.atomcoords[conf][neighbors_indices][0]
|
|
310
|
+
|
|
311
|
+
self.vector = self.other - self.coord # vector connecting center to substituent
|
|
312
|
+
|
|
313
|
+
if update:
|
|
314
|
+
if orb_dim is None:
|
|
315
|
+
key = self.symbol + ' ' + str(self).split(' (')[0]
|
|
316
|
+
orb_dim = orb_dim_dict.get(key)
|
|
317
|
+
|
|
318
|
+
if orb_dim is None:
|
|
319
|
+
orb_dim = orb_dim_dict['Fallback']
|
|
320
|
+
print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using {orb_dim} A.')
|
|
321
|
+
|
|
322
|
+
neighbors_of_neighbor_indices = neighbors(mol.graph, neighbors_indices[0])
|
|
323
|
+
neighbors_of_neighbor_indices.remove(i)
|
|
324
|
+
|
|
325
|
+
self.vector = norm(self.vector)*orb_dim
|
|
326
|
+
|
|
327
|
+
if len(neighbors_of_neighbor_indices) == 1:
|
|
328
|
+
# ketene
|
|
329
|
+
|
|
330
|
+
ketene_sub_indices = neighbors(mol.graph, neighbors_of_neighbor_indices[0])
|
|
331
|
+
ketene_sub_indices.remove(neighbors_indices[0])
|
|
332
|
+
|
|
333
|
+
ketene_sub_coords = mol.atomcoords[conf][ketene_sub_indices[0]]
|
|
334
|
+
n_o_n_coords = mol.atomcoords[conf][neighbors_of_neighbor_indices[0]]
|
|
335
|
+
|
|
336
|
+
# vector connecting ketene R with C (O=C=C(R)R)
|
|
337
|
+
v = (ketene_sub_coords - n_o_n_coords)
|
|
338
|
+
|
|
339
|
+
# this vector is orthogonal to the ketene O=C=C and coplanar with the ketene
|
|
340
|
+
pointer = v - ((v @ norm(self.vector)) * self.vector)
|
|
341
|
+
pointer = norm(pointer) * orb_dim
|
|
342
|
+
|
|
343
|
+
self.center = np.array([rot_mat_from_pointer(self.vector, 90*step) @ pointer for step in range(4)])
|
|
344
|
+
|
|
345
|
+
self.subtype = 'p+p'
|
|
346
|
+
|
|
347
|
+
elif len(neighbors_of_neighbor_indices) == 2:
|
|
348
|
+
# if it is a normal ketone (or an enolate), n orbital lobes must be coplanar with
|
|
349
|
+
# atoms connecting to ketone C atom, or p lobes must be placed accordingly
|
|
350
|
+
|
|
351
|
+
a1 = mol.atomcoords[conf][neighbors_of_neighbor_indices[0]]
|
|
352
|
+
a2 = mol.atomcoords[conf][neighbors_of_neighbor_indices[1]]
|
|
353
|
+
pivot = norm(np.cross(a1 - self.coord, a2 - self.coord))
|
|
354
|
+
|
|
355
|
+
if mol.sigmatropic[conf]:
|
|
356
|
+
# two p lobes
|
|
357
|
+
self.center = np.concatenate(([pivot*orb_dim], [-pivot*orb_dim]))
|
|
358
|
+
self.subtype = 'p'
|
|
359
|
+
|
|
360
|
+
else:
|
|
361
|
+
#two n lobes
|
|
362
|
+
self.center = np.array([rot_mat_from_pointer(pivot, angle) @ self.vector for angle in (120,240)])
|
|
363
|
+
self.subtype = 'sp2'
|
|
364
|
+
|
|
365
|
+
elif len(neighbors_of_neighbor_indices) == 3:
|
|
366
|
+
# alkoxide, sulfonamide
|
|
367
|
+
|
|
368
|
+
v1, v2, v3 = mol.atomcoords[conf][neighbors_of_neighbor_indices] - self.coord
|
|
369
|
+
v1, v2, v3 = norm(v1), norm(v2), norm(v3)
|
|
370
|
+
v1, v2, v3 = v1*orb_dim, v2*orb_dim, v3*orb_dim
|
|
371
|
+
pivot = norm(np.cross(self.vector, v1))
|
|
372
|
+
|
|
373
|
+
self.center = np.array([rot_mat_from_pointer(pivot, 180) @ v for v in (v1, v2, v3)])
|
|
374
|
+
self.subtype = 'trilobe'
|
|
375
|
+
|
|
376
|
+
self.orb_vecs = np.array([norm(center) for center in self.center])
|
|
377
|
+
# unit vectors connecting reactive atom coord with orbital centers
|
|
378
|
+
|
|
379
|
+
self.center += self.coord
|
|
380
|
+
# two vectors defining the position of the two orbital lobes centers
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class Imine:
|
|
384
|
+
|
|
385
|
+
def __repr__(self):
|
|
386
|
+
return 'Imine'
|
|
387
|
+
|
|
388
|
+
def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
|
|
389
|
+
'''
|
|
390
|
+
'''
|
|
391
|
+
self.index = i
|
|
392
|
+
self.symbol = pt[mol.atomnos[i]].symbol
|
|
393
|
+
neighbors_indices = neighbors(mol.graph, i)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indices]
|
|
398
|
+
self.coord = mol.atomcoords[conf][i]
|
|
399
|
+
self.others = mol.atomcoords[conf][neighbors_indices]
|
|
400
|
+
|
|
401
|
+
self.vectors = self.others - self.coord # vector connecting center to substituent
|
|
402
|
+
|
|
403
|
+
if update:
|
|
404
|
+
if orb_dim is None:
|
|
405
|
+
key = self.symbol + ' ' + str(self).split(' (')[0]
|
|
406
|
+
orb_dim = orb_dim_dict.get(key)
|
|
407
|
+
|
|
408
|
+
if orb_dim is None:
|
|
409
|
+
orb_dim = orb_dim_dict['Fallback']
|
|
410
|
+
print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using {orb_dim} A.')
|
|
411
|
+
|
|
412
|
+
if mol.sigmatropic[conf]:
|
|
413
|
+
# two p lobes
|
|
414
|
+
p_lobe = norm(np.cross(self.vectors[0], self.vectors[1]))*orb_dim
|
|
415
|
+
self.orb_vecs = np.concatenate(([p_lobe], [-p_lobe]))
|
|
416
|
+
|
|
417
|
+
else:
|
|
418
|
+
# lone pair lobe
|
|
419
|
+
self.orb_vecs = np.array([-norm(np.mean([norm(v) for v in self.vectors], axis=0))*orb_dim])
|
|
420
|
+
|
|
421
|
+
self.center = self.orb_vecs + self.coord
|
|
422
|
+
# two vectors defining the position of the two orbital lobes centers
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
class Sp_or_carbene:
|
|
426
|
+
|
|
427
|
+
def __repr__(self):
|
|
428
|
+
return self.type
|
|
429
|
+
|
|
430
|
+
def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
|
|
431
|
+
|
|
432
|
+
self.index = i
|
|
433
|
+
self.symbol = pt[mol.atomnos[i]].symbol
|
|
434
|
+
neighbors_indices = neighbors(mol.graph, i)
|
|
435
|
+
|
|
436
|
+
self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indices]
|
|
437
|
+
|
|
438
|
+
self.coord = mol.atomcoords[conf][i]
|
|
439
|
+
self.others = mol.atomcoords[conf][neighbors_indices]
|
|
440
|
+
|
|
441
|
+
self.vectors = self.others - self.coord # vector connecting center to substituent
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
angle = vec_angle(norm(self.others[0] - self.coord),
|
|
445
|
+
norm(self.others[1] - self.coord))
|
|
446
|
+
|
|
447
|
+
if np.abs(angle - 180) < 5:
|
|
448
|
+
self.type = 'sp'
|
|
449
|
+
|
|
450
|
+
else:
|
|
451
|
+
self.type = 'bent carbene'
|
|
452
|
+
|
|
453
|
+
self.allene = False
|
|
454
|
+
self.ketene = False
|
|
455
|
+
if self.type == 'sp' and all([s == 'C' for s in self.neighbors_symbols]):
|
|
456
|
+
|
|
457
|
+
neighbors_of_neighbors_indices = (neighbors(mol.graph, neighbors_indices[0]),
|
|
458
|
+
neighbors(mol.graph, neighbors_indices[1]))
|
|
459
|
+
|
|
460
|
+
neighbors_of_neighbors_indices[0].remove(i)
|
|
461
|
+
neighbors_of_neighbors_indices[1].remove(i)
|
|
462
|
+
|
|
463
|
+
if (len(side1) == len(side2) == 2 for side1, side2 in neighbors_of_neighbors_indices):
|
|
464
|
+
self.allene = True
|
|
465
|
+
|
|
466
|
+
elif self.type == 'sp' and sorted(self.neighbors_symbols) in (['C', 'O'], ['C', 'S']):
|
|
467
|
+
|
|
468
|
+
self.ketene = True
|
|
469
|
+
|
|
470
|
+
neighbors_of_neighbors_indices = (neighbors(mol.graph, neighbors_indices[0]),
|
|
471
|
+
neighbors(mol.graph, neighbors_indices[1]))
|
|
472
|
+
|
|
473
|
+
neighbors_of_neighbors_indices[0].remove(i)
|
|
474
|
+
neighbors_of_neighbors_indices[1].remove(i)
|
|
475
|
+
|
|
476
|
+
if len(neighbors_of_neighbors_indices[0]) == 2:
|
|
477
|
+
substituent = mol.atomcoords[conf][neighbors_of_neighbors_indices[0][0]]
|
|
478
|
+
ketene_atom = mol.atomcoords[conf][neighbors_indices[0]]
|
|
479
|
+
self.ketene_ref = substituent - ketene_atom
|
|
480
|
+
|
|
481
|
+
elif len(neighbors_of_neighbors_indices[1]) == 2:
|
|
482
|
+
substituent = mol.atomcoords[conf][neighbors_of_neighbors_indices[1][0]]
|
|
483
|
+
ketene_atom = mol.atomcoords[conf][neighbors_indices[1]]
|
|
484
|
+
self.ketene_ref = substituent - ketene_atom
|
|
485
|
+
|
|
486
|
+
else:
|
|
487
|
+
self.ketene = False
|
|
488
|
+
|
|
489
|
+
if update:
|
|
490
|
+
if orb_dim is None:
|
|
491
|
+
key = self.symbol + ' ' + self.type
|
|
492
|
+
orb_dim = orb_dim_dict.get(key)
|
|
493
|
+
|
|
494
|
+
if orb_dim is None:
|
|
495
|
+
orb_dim = orb_dim_dict['Fallback']
|
|
496
|
+
print(f'ATTENTION: COULD NOT SETUP REACTIVE ATOM ORBITAL FROM PARAMETERS. We have no parameters for {key}. Using {orb_dim} A.')
|
|
497
|
+
|
|
498
|
+
if self.type == 'sp':
|
|
499
|
+
|
|
500
|
+
v = np.random.rand(3)
|
|
501
|
+
pivot1 = v - ((v @ norm(self.vectors[0])) * self.vectors[0])
|
|
502
|
+
|
|
503
|
+
if self.allene or self.ketene:
|
|
504
|
+
# if we have an allene or ketene, pivot1 is aligned to
|
|
505
|
+
# one substituent so that the resulting positions
|
|
506
|
+
# for the four orbital centers make chemical sense.
|
|
507
|
+
|
|
508
|
+
axis = norm(self.others[0] - self.others[1])
|
|
509
|
+
# versor connecting reactive atom neighbors
|
|
510
|
+
|
|
511
|
+
if self.allene:
|
|
512
|
+
ref = (mol.atomcoords[conf][neighbors_of_neighbors_indices[0][0]] -
|
|
513
|
+
mol.atomcoords[conf][neighbors_indices[0]])
|
|
514
|
+
else:
|
|
515
|
+
ref = self.ketene_ref
|
|
516
|
+
|
|
517
|
+
pivot1 = ref - ref @ axis * axis
|
|
518
|
+
# projection of ref orthogonal to axis (vector rejection)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
pivot2 = norm(np.cross(pivot1, self.vectors[0]))
|
|
522
|
+
|
|
523
|
+
self.orb_vecs = np.array([rot_mat_from_pointer(pivot2, 90) @
|
|
524
|
+
rot_mat_from_pointer(pivot1, angle) @
|
|
525
|
+
norm(self.vectors[0]) for angle in (0, 90, 180, 270)]) * orb_dim
|
|
526
|
+
|
|
527
|
+
self.center = self.orb_vecs + self.coord
|
|
528
|
+
# four vectors defining the position of the four orbital lobes centers
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
else: # bent carbene case: three centers, sp2+p
|
|
533
|
+
|
|
534
|
+
self.orb_vecs = np.array([-norm(np.mean([norm(v) for v in self.vectors], axis=0))*orb_dim])
|
|
535
|
+
# one sp2 center first
|
|
536
|
+
|
|
537
|
+
p_vec = np.cross(norm(self.vectors[0]), norm(self.vectors[1]))
|
|
538
|
+
p_vecs = np.array([norm(p_vec)*orb_dim, -norm(p_vec)*orb_dim])
|
|
539
|
+
self.orb_vecs = np.concatenate((self.orb_vecs, p_vecs))
|
|
540
|
+
# adding two p centers
|
|
541
|
+
|
|
542
|
+
self.center = self.orb_vecs + self.coord
|
|
543
|
+
# three vectors defining the position of the two p lobes and main sp2 lobe centers
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
class Metal:
|
|
547
|
+
|
|
548
|
+
def __repr__(self):
|
|
549
|
+
return 'Metal'
|
|
550
|
+
|
|
551
|
+
def init(self, mol, i, update=False, orb_dim=None, conf=0) -> None:
|
|
552
|
+
|
|
553
|
+
self.index = i
|
|
554
|
+
self.symbol = pt[mol.atomnos[i]].symbol
|
|
555
|
+
neighbors_indices = neighbors(mol.graph, i)
|
|
556
|
+
|
|
557
|
+
self.neighbors_symbols = [pt[mol.atomnos[i]].symbol for i in neighbors_indices]
|
|
558
|
+
self.coord = mol.atomcoords[conf][i]
|
|
559
|
+
self.others = mol.atomcoords[conf][neighbors_indices]
|
|
560
|
+
|
|
561
|
+
self.vectors = self.others - self.coord # vectors connecting reactive atom with neighbors
|
|
562
|
+
|
|
563
|
+
v1 = self.vectors[0]
|
|
564
|
+
# v1 connects first bonded atom to the metal itself
|
|
565
|
+
|
|
566
|
+
neighbor_of_neighbor_index = neighbors(mol.graph, neighbors_indices[0])[0]
|
|
567
|
+
v2 = mol.atomcoords[conf][neighbor_of_neighbor_index] - self.coord
|
|
568
|
+
# v2 connects first neighbor of the first neighbor to the metal itself
|
|
569
|
+
|
|
570
|
+
self.orb_vec = norm(rot_mat_from_pointer(np.cross(v1, v2), 120) @ v1)
|
|
571
|
+
# setting the pointer (orb_vec) so that orbitals are oriented correctly
|
|
572
|
+
# (Lithium enolate in mind)
|
|
573
|
+
|
|
574
|
+
steps = 4 # number of total orbitals
|
|
575
|
+
self.orb_vecs = np.array([rot_mat_from_pointer(v1, angle) @ self.orb_vec for angle in range(0,360,int(360/steps))])
|
|
576
|
+
|
|
577
|
+
if update:
|
|
578
|
+
if orb_dim is None:
|
|
579
|
+
orb_dim = orb_dim_dict[str(self)]
|
|
580
|
+
|
|
581
|
+
self.center = (self.orb_vecs * orb_dim) + self.coord
|
|
582
|
+
|
|
583
|
+
# Keys are made of atom symbol and number of bonds that it makes
|
|
584
|
+
atom_type_dict = {
|
|
585
|
+
'H1' : Single,
|
|
586
|
+
|
|
587
|
+
'B3' : Sp2,
|
|
588
|
+
'B4' : Sp3,
|
|
589
|
+
|
|
590
|
+
'C1' : Single, # deprotonated terminal alkyne. What if it is a carbylidene? Very rare by the way...
|
|
591
|
+
'C2' : Sp_or_carbene, # sp if straight, carbene if bent
|
|
592
|
+
'C3' : Sp2, # double ball
|
|
593
|
+
'C4' : Sp3, # one ball, on the back of the leaving group. If we can't tell which one it is, we ask user
|
|
594
|
+
|
|
595
|
+
'N1' : Single,
|
|
596
|
+
'N2' : Imine, # one ball on free side
|
|
597
|
+
'N3' : Sp2, # double ball
|
|
598
|
+
'N4' : Sp3, # leaving group
|
|
599
|
+
|
|
600
|
+
'O1' : Ketone, # two balls 120° apart. Also for alkoxides, good enough
|
|
601
|
+
'O2' : Ether, # or alcohol, two balls about 109,5° apart
|
|
602
|
+
|
|
603
|
+
'P2' : Imine, # one ball on free side
|
|
604
|
+
'P3' : Sp2, # double ball
|
|
605
|
+
'P4' : Sp3, # leaving group
|
|
606
|
+
|
|
607
|
+
'S1' : Ketone,
|
|
608
|
+
'S2' : Ether,
|
|
609
|
+
'S3' : Sp2, # Not sure if this can be valid, but it's basically treating it as a bent carbonyl, should work
|
|
610
|
+
# 'S3' : Sulphoxide, # Should we consider this? Or just ok with Sp2()?
|
|
611
|
+
# 'S4' : Sulphone,
|
|
612
|
+
|
|
613
|
+
'F1' : Single,
|
|
614
|
+
'Cl1': Single,
|
|
615
|
+
'Br1': Single,
|
|
616
|
+
'I1' : Single,
|
|
617
|
+
|
|
618
|
+
############### Name associations
|
|
619
|
+
|
|
620
|
+
'Single' : Single,
|
|
621
|
+
'Sp2' : Sp2,
|
|
622
|
+
'Sp3' : Sp3,
|
|
623
|
+
'Ether' : Ether,
|
|
624
|
+
'Ketone' : Ketone,
|
|
625
|
+
'Imine' : Imine,
|
|
626
|
+
'Sp_or_carbene' : Sp_or_carbene,
|
|
627
|
+
'Metal' : Metal,
|
|
628
|
+
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
metals = (
|
|
632
|
+
'Li',
|
|
633
|
+
'Na',
|
|
634
|
+
'Mg',
|
|
635
|
+
'K',
|
|
636
|
+
'Ca',
|
|
637
|
+
'Ti',
|
|
638
|
+
'Rb',
|
|
639
|
+
'Sr',
|
|
640
|
+
'Cs',
|
|
641
|
+
'Ba',
|
|
642
|
+
'Zn',
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
for metal in metals:
|
|
646
|
+
for bonds in range(1,9):
|
|
647
|
+
bonds = str(bonds)
|
|
648
|
+
atom_type_dict[metal+bonds] = Metal
|
|
649
|
+
|
|
650
|
+
def get_atom_type(graph, index, override=None):
|
|
651
|
+
'''
|
|
652
|
+
Returns the appropriate class to represent
|
|
653
|
+
the atom with the given index on the graph.
|
|
654
|
+
If override is not None, returns the class
|
|
655
|
+
with that name.
|
|
656
|
+
'''
|
|
657
|
+
if override is not None:
|
|
658
|
+
return atom_type_dict[override]
|
|
659
|
+
|
|
660
|
+
nb = neighbors(graph, index)
|
|
661
|
+
code = pt[graph.nodes[index]['atomnos']].symbol + str(len(nb))
|
|
662
|
+
try:
|
|
663
|
+
return atom_type_dict[code]
|
|
664
|
+
|
|
665
|
+
except KeyError:
|
|
666
|
+
raise KeyError(f'Orbital type {code} not known (index {index})')
|
firecode/references.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
|
|
3
|
+
from firecode.__main__ import __version__
|
|
4
|
+
|
|
5
|
+
references = {
|
|
6
|
+
'FIRECODE' : f'Tampellini, N. FIRECODE {__version__}, Github - https://github.com/ntampellini/firecode',
|
|
7
|
+
'GFN-FF' : 'Spicher, S.; Grimme, S. Angew. Chem. Int. Ed. 2020, 59, 15665-15673',
|
|
8
|
+
'GFN2-XTB': 'Bannwarth, C.; Ehlert, S.; Grimme, S. J. Chem. Theory Comput. 2019, 15, 1652-1671',
|
|
9
|
+
'CREST': 'Pracht, P.; Bohle, F.; Grimme, S. PCCP 2020, 22, 7169-7192',
|
|
10
|
+
'ALPB': 'Ehlert, S.; Stahn, M.; Spicher, S.; Grimme, S. J. Chem. Theory Comput. 2021, 17, 4250-4261',
|
|
11
|
+
}
|