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/errors.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
class ZeroCandidatesError(Exception):
|
|
24
|
+
'''
|
|
25
|
+
Raised at any time during run if all
|
|
26
|
+
candidates are discarded.
|
|
27
|
+
'''
|
|
28
|
+
|
|
29
|
+
class InputError(Exception):
|
|
30
|
+
'''
|
|
31
|
+
Raised when reading the input file if
|
|
32
|
+
something is wrong.
|
|
33
|
+
'''
|
|
34
|
+
|
|
35
|
+
class TriangleError(Exception):
|
|
36
|
+
'''
|
|
37
|
+
Raised from polygonize if it cannot build
|
|
38
|
+
a triangle with the given side lengths.
|
|
39
|
+
'''
|
|
40
|
+
|
|
41
|
+
class CCReadError(Exception):
|
|
42
|
+
'''
|
|
43
|
+
Raised when CCRead cannot read
|
|
44
|
+
the provided filename.
|
|
45
|
+
'''
|
|
46
|
+
|
|
47
|
+
class MopacReadError(Exception):
|
|
48
|
+
'''
|
|
49
|
+
Thrown when reading MOPAC output files fails for some reason.
|
|
50
|
+
'''
|
|
51
|
+
|
|
52
|
+
class SegmentedGraphError(Exception):
|
|
53
|
+
'''
|
|
54
|
+
Thrown by Clustered CSearch when graph has more than one connected component.
|
|
55
|
+
'''
|
|
56
|
+
|
|
57
|
+
class NoOrbitalError(Exception):
|
|
58
|
+
'''
|
|
59
|
+
Thrown when trying to access orbital data when they are not present
|
|
60
|
+
'''
|
|
61
|
+
|
|
62
|
+
class FatalError(Exception):
|
|
63
|
+
'''
|
|
64
|
+
Thrown when a molecule optimization crashed or scrambled fatally
|
|
65
|
+
'''
|
|
@@ -0,0 +1,333 @@
|
|
|
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
|
+
from copy import deepcopy
|
|
24
|
+
from itertools import combinations
|
|
25
|
+
|
|
26
|
+
import numpy as np
|
|
27
|
+
from networkx import (all_simple_paths, connected_components,
|
|
28
|
+
from_numpy_matrix, get_node_attributes,
|
|
29
|
+
set_node_attributes)
|
|
30
|
+
|
|
31
|
+
from firecode.algebra import all_dists, dihedral, norm_of
|
|
32
|
+
from firecode.pt import pt
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def d_min_bond(e1, e2):
|
|
36
|
+
return 1.2 * (pt[e1].covalent_radius + pt[e2].covalent_radius)
|
|
37
|
+
# return 0.2 + (pt[e1].covalent_radius + pt[e2].covalent_radius)
|
|
38
|
+
# if this is somewhat prone to bugs, this might help https://cccbdb.nist.gov/calcbondcomp1x.asp
|
|
39
|
+
|
|
40
|
+
def graphize(coords, atomnos, mask=None):
|
|
41
|
+
'''
|
|
42
|
+
:params coords: atomic coordinates as 3D vectors
|
|
43
|
+
:params atomnos: atomic numbers as a list
|
|
44
|
+
:params mask: bool array, with False for atoms
|
|
45
|
+
to be excluded in the bond evaluation
|
|
46
|
+
:return connectivity graph
|
|
47
|
+
|
|
48
|
+
'''
|
|
49
|
+
|
|
50
|
+
mask = np.array([True for _ in atomnos], dtype=bool) if mask is None else mask
|
|
51
|
+
|
|
52
|
+
matrix = np.zeros((len(coords),len(coords)))
|
|
53
|
+
for i, _ in enumerate(coords):
|
|
54
|
+
for j in range(i,len(coords)):
|
|
55
|
+
if mask[i] and mask[j]:
|
|
56
|
+
if norm_of(coords[i]-coords[j]) < d_min_bond(atomnos[i], atomnos[j]):
|
|
57
|
+
matrix[i][j] = 1
|
|
58
|
+
|
|
59
|
+
graph = from_numpy_matrix(matrix)
|
|
60
|
+
set_node_attributes(graph, dict(enumerate(atomnos)), 'atomnos')
|
|
61
|
+
|
|
62
|
+
return graph
|
|
63
|
+
|
|
64
|
+
def neighbors(graph, index):
|
|
65
|
+
# neighbors = list([(a, b) for a, b in graph.adjacency()][index][1].keys())
|
|
66
|
+
neighbors = list(graph.neighbors(index))
|
|
67
|
+
if index in neighbors:
|
|
68
|
+
neighbors.remove(index)
|
|
69
|
+
return neighbors
|
|
70
|
+
|
|
71
|
+
def is_sp_n(index, graph, n):
|
|
72
|
+
'''
|
|
73
|
+
Returns True if the sp_n value matches the input
|
|
74
|
+
'''
|
|
75
|
+
sp_n = get_sp_n(index, graph)
|
|
76
|
+
if sp_n == n:
|
|
77
|
+
return True
|
|
78
|
+
return False
|
|
79
|
+
|
|
80
|
+
def get_sp_n(index, graph):
|
|
81
|
+
'''
|
|
82
|
+
Returns n, that is the apex of sp^n hybridization for CONPS atoms.
|
|
83
|
+
This is just an assimilation to the carbon geometry in relation to sp^n:
|
|
84
|
+
- sp(1) is linear
|
|
85
|
+
- sp2 is planar
|
|
86
|
+
- sp3 is tetraedral
|
|
87
|
+
This is mainly used to understand if a torsion is to be rotated or not.
|
|
88
|
+
'''
|
|
89
|
+
element = graph.nodes[index]['atomnos']
|
|
90
|
+
|
|
91
|
+
if element not in (6,7,8,15,16):
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
d = {
|
|
95
|
+
6:{2:1, 3:2, 4:3}, # C - 2 neighbors means sp, 3 nb means sp2, 4 nb sp3
|
|
96
|
+
7:{2:2, 3:None, 4:3}, # N - 2 neighbors means sp2, 3 nb could mean sp3 or sp2, 4 nb sp3
|
|
97
|
+
8:{1:2, 2:3, 3:3, 4:3}, # O
|
|
98
|
+
15:{2:2, 3:3, 4:3}, # P - like N
|
|
99
|
+
16:{2:2, 3:3, 4:3}, # S
|
|
100
|
+
}
|
|
101
|
+
return d[element].get(len(neighbors(graph, index)))
|
|
102
|
+
|
|
103
|
+
def is_amide_n(index, graph, mode=-1):
|
|
104
|
+
'''
|
|
105
|
+
Returns true if the nitrogen atom at the given
|
|
106
|
+
index is a nitrogen and is part of an amide.
|
|
107
|
+
Carbamates and ureas are considered amides.
|
|
108
|
+
|
|
109
|
+
mode:
|
|
110
|
+
-1 - any amide
|
|
111
|
+
0 - primary amide (CONH2)
|
|
112
|
+
1 - secondary amide (CONHR)
|
|
113
|
+
2 - tertiary amide (CONR2)
|
|
114
|
+
'''
|
|
115
|
+
if graph.nodes[index]['atomnos'] == 7:
|
|
116
|
+
# index must be a nitrogen atom
|
|
117
|
+
|
|
118
|
+
nb = neighbors(graph, index)
|
|
119
|
+
nb_atomnos = [graph.nodes[j]['atomnos'] for j in nb]
|
|
120
|
+
|
|
121
|
+
if mode != -1:
|
|
122
|
+
if nb_atomnos.count(1) != (2,1,0)[mode]:
|
|
123
|
+
# primary amides need to have 1H, secondary amides none
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
for n in nb:
|
|
127
|
+
if graph.nodes[n]['atomnos'] == 6:
|
|
128
|
+
# there must be at least one carbon atom next to N
|
|
129
|
+
|
|
130
|
+
nb_nb = neighbors(graph, n)
|
|
131
|
+
if len(nb_nb) == 3:
|
|
132
|
+
# bonded to three atoms
|
|
133
|
+
|
|
134
|
+
nb_nb_sym = [graph.nodes[i]['atomnos'] for i in nb_nb]
|
|
135
|
+
if 8 in nb_nb_sym:
|
|
136
|
+
return True
|
|
137
|
+
# and at least one of them has to be an oxygen
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
def is_ester_o(index, graph):
|
|
141
|
+
'''
|
|
142
|
+
Returns true if the atom at the given
|
|
143
|
+
index is an oxygen and is part of an ester.
|
|
144
|
+
Carbamates and carbonates return True,
|
|
145
|
+
Carboxylic acids return False.
|
|
146
|
+
'''
|
|
147
|
+
if graph.nodes[index]['atomnos'] == 8:
|
|
148
|
+
nb = neighbors(graph, index)
|
|
149
|
+
if 1 not in nb:
|
|
150
|
+
for n in nb:
|
|
151
|
+
if graph.nodes[n]['atomnos'] == 6:
|
|
152
|
+
nb_nb = neighbors(graph, n)
|
|
153
|
+
if len(nb_nb) == 3:
|
|
154
|
+
nb_nb_sym = [graph.nodes[i]['atomnos'] for i in nb_nb]
|
|
155
|
+
if nb_nb_sym.count(8) > 1:
|
|
156
|
+
return True
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
def is_phenyl(coords):
|
|
160
|
+
'''
|
|
161
|
+
:params coords: six coordinates of C/N atoms
|
|
162
|
+
:return tuple: bool indicating if the six atoms look like part of a
|
|
163
|
+
phenyl/naphtyl/pyridine system, coordinates for the center of that ring
|
|
164
|
+
|
|
165
|
+
NOTE: quinones would show as aromatic: it is okay, since they can do π-stacking as well.
|
|
166
|
+
'''
|
|
167
|
+
|
|
168
|
+
if np.max(all_dists(coords, coords)) > 3:
|
|
169
|
+
return False
|
|
170
|
+
# if any atomic couple is more than 3 A away from each other, this is not a Ph
|
|
171
|
+
|
|
172
|
+
threshold_delta = 1 - np.cos(10 * np.pi/180)
|
|
173
|
+
flat_delta = 1 - np.abs(np.cos(dihedral(coords[[0,1,2,3]]) * np.pi/180))
|
|
174
|
+
|
|
175
|
+
if flat_delta < threshold_delta:
|
|
176
|
+
flat_delta = 1 - np.abs(np.cos(dihedral(coords[[0,1,2,3]]) * np.pi/180))
|
|
177
|
+
if flat_delta < threshold_delta:
|
|
178
|
+
# print('phenyl center at', np.mean(coords, axis=0))
|
|
179
|
+
return True
|
|
180
|
+
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
def get_phenyls(coords, atomnos):
|
|
184
|
+
'''
|
|
185
|
+
returns a (n, 6, 3) array where the first
|
|
186
|
+
dimension is the aromatic rings detected
|
|
187
|
+
'''
|
|
188
|
+
if len(atomnos) < 6:
|
|
189
|
+
return np.array([])
|
|
190
|
+
|
|
191
|
+
output = []
|
|
192
|
+
|
|
193
|
+
c_n_indices = np.fromiter((i for i, a in enumerate(atomnos) if a in (6,7)), dtype=atomnos.dtype)
|
|
194
|
+
comb = combinations(c_n_indices, 6)
|
|
195
|
+
|
|
196
|
+
for c in comb:
|
|
197
|
+
mask = np.fromiter((i in c for i in range(len(atomnos))), dtype=bool)
|
|
198
|
+
coords_ = coords[mask]
|
|
199
|
+
if is_phenyl(coords_):
|
|
200
|
+
output.append(coords_)
|
|
201
|
+
|
|
202
|
+
return np.array(output)
|
|
203
|
+
|
|
204
|
+
def _get_phenyl_ids(i, G):
|
|
205
|
+
'''
|
|
206
|
+
If i is part of a phenyl group, return the six
|
|
207
|
+
heavy atom indices associated with the ring
|
|
208
|
+
'''
|
|
209
|
+
for n in neighbors(G, i):
|
|
210
|
+
paths = all_simple_paths(G, source=i, target=n, cutoff=6)
|
|
211
|
+
for path in paths:
|
|
212
|
+
if len(path) == 6:
|
|
213
|
+
if all(G.nodes[n]['atomnos'] != 1 for n in path):
|
|
214
|
+
if all(len(neighbors(G, i)) == 3 for i in path):
|
|
215
|
+
return path
|
|
216
|
+
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
def findPaths(G, u, n, excludeSet = None):
|
|
220
|
+
'''
|
|
221
|
+
Recursively find all paths of a NetworkX
|
|
222
|
+
graph G with length = n, starting from node u
|
|
223
|
+
'''
|
|
224
|
+
if excludeSet is None:
|
|
225
|
+
excludeSet = set([u])
|
|
226
|
+
|
|
227
|
+
else:
|
|
228
|
+
excludeSet.add(u)
|
|
229
|
+
|
|
230
|
+
if n == 0:
|
|
231
|
+
return [[u]]
|
|
232
|
+
|
|
233
|
+
paths = [[u]+path for neighbor in G.neighbors(u) if neighbor not in excludeSet for path in findPaths(G,neighbor,n-1,excludeSet)]
|
|
234
|
+
excludeSet.remove(u)
|
|
235
|
+
|
|
236
|
+
return paths
|
|
237
|
+
|
|
238
|
+
def is_sigmatropic(mol, conf):
|
|
239
|
+
'''
|
|
240
|
+
mol: Hypermolecule object
|
|
241
|
+
conf: conformer index
|
|
242
|
+
|
|
243
|
+
A hypermolecule is considered sigmatropic when:
|
|
244
|
+
- has 2 reactive atoms
|
|
245
|
+
- they are of sp2 or analogous types
|
|
246
|
+
- they are connected, or at least one path connecting them
|
|
247
|
+
is made up of atoms that do not make more than three bonds each
|
|
248
|
+
- they are less than 3 A apart (cisoid propenal makes it, transoid does not)
|
|
249
|
+
|
|
250
|
+
Used to set the mol.sigmatropic attribute, that affects orbital
|
|
251
|
+
building (p or n lobes) for Ketone and Imine reactive atoms classes.
|
|
252
|
+
'''
|
|
253
|
+
sp2_types = (
|
|
254
|
+
'Ketone',
|
|
255
|
+
'Imine',
|
|
256
|
+
'sp2',
|
|
257
|
+
'sp',
|
|
258
|
+
'bent carbene'
|
|
259
|
+
)
|
|
260
|
+
if len(mol.reactive_indices) == 2:
|
|
261
|
+
|
|
262
|
+
i1, i2 = mol.reactive_indices
|
|
263
|
+
if norm_of(mol.atomcoords[conf][i1] - mol.atomcoords[conf][i2]) < 3:
|
|
264
|
+
|
|
265
|
+
if all([str(r_atom) in sp2_types for r_atom in mol.reactive_atoms_classes_dict[conf].values()]):
|
|
266
|
+
|
|
267
|
+
paths = all_simple_paths(mol.graph, i1, i2)
|
|
268
|
+
|
|
269
|
+
for path in paths:
|
|
270
|
+
path = path[1:-1]
|
|
271
|
+
|
|
272
|
+
full_sp2 = True
|
|
273
|
+
for index in path:
|
|
274
|
+
if len(neighbors(mol.graph, index))-2 > 1:
|
|
275
|
+
full_sp2 = False
|
|
276
|
+
break
|
|
277
|
+
|
|
278
|
+
if full_sp2:
|
|
279
|
+
return True
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
def is_vicinal(mol):
|
|
283
|
+
'''
|
|
284
|
+
A hypermolecule is considered vicinal when:
|
|
285
|
+
- has 2 reactive atoms
|
|
286
|
+
- they are of sp3 or Single Bond type
|
|
287
|
+
- they are bonded
|
|
288
|
+
|
|
289
|
+
Used to set the mol.sp3_sigmastar attribute, that affects orbital
|
|
290
|
+
building (BH4 or agostic-like behavior) for Sp3 and Single Bond reactive atoms classes.
|
|
291
|
+
'''
|
|
292
|
+
vicinal_types = (
|
|
293
|
+
'sp3',
|
|
294
|
+
'Single Bond',
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
if len(mol.reactive_indices) == 2:
|
|
298
|
+
|
|
299
|
+
i1, i2 = mol.reactive_indices
|
|
300
|
+
|
|
301
|
+
if all([str(r_atom) in vicinal_types for r_atom in mol.reactive_atoms_classes_dict[0].values()]):
|
|
302
|
+
if i1 in neighbors(mol.graph, i2):
|
|
303
|
+
return True
|
|
304
|
+
|
|
305
|
+
return False
|
|
306
|
+
|
|
307
|
+
def get_sum_graph(graphs, extra_edges=None):
|
|
308
|
+
'''
|
|
309
|
+
Creates a graph containing all graphs, added in
|
|
310
|
+
sequence, and then adds the specified extra edges
|
|
311
|
+
(with cumulative numbering).
|
|
312
|
+
'''
|
|
313
|
+
|
|
314
|
+
graph, *extra = graphs
|
|
315
|
+
out = deepcopy(graph)
|
|
316
|
+
cum_atomnos = list(get_node_attributes(graphs[0], "atomnos").values())
|
|
317
|
+
|
|
318
|
+
for g in extra:
|
|
319
|
+
n = len(out.nodes())
|
|
320
|
+
for e1, e2 in g.edges():
|
|
321
|
+
out.add_edge(e1+n, e2+n)
|
|
322
|
+
|
|
323
|
+
cum_atomnos += list(get_node_attributes(g, "atomnos").values())
|
|
324
|
+
|
|
325
|
+
out.is_single_molecule = (len(list(connected_components(out))) == 1)
|
|
326
|
+
|
|
327
|
+
if extra_edges is not None:
|
|
328
|
+
for e1, e2 in extra_edges:
|
|
329
|
+
out.add_edge(e1, e2)
|
|
330
|
+
|
|
331
|
+
set_node_attributes(out, dict(enumerate(cum_atomnos)), 'atomnos')
|
|
332
|
+
|
|
333
|
+
return out
|