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.
Files changed (59) hide show
  1. firecode/TEST_NOTEBOOK.ipynb +3940 -0
  2. firecode/__init__.py +0 -0
  3. firecode/__main__.py +118 -0
  4. firecode/_gaussian.py +97 -0
  5. firecode/algebra.py +405 -0
  6. firecode/ase_manipulations.py +879 -0
  7. firecode/atropisomer_module.py +516 -0
  8. firecode/automep.py +130 -0
  9. firecode/calculators/__init__.py +29 -0
  10. firecode/calculators/_gaussian.py +98 -0
  11. firecode/calculators/_mopac.py +242 -0
  12. firecode/calculators/_openbabel.py +154 -0
  13. firecode/calculators/_orca.py +129 -0
  14. firecode/calculators/_xtb.py +786 -0
  15. firecode/concurrent_test.py +119 -0
  16. firecode/embedder.py +2590 -0
  17. firecode/embedder_options.py +577 -0
  18. firecode/embeds.py +881 -0
  19. firecode/errors.py +65 -0
  20. firecode/graph_manipulations.py +333 -0
  21. firecode/hypermolecule_class.py +364 -0
  22. firecode/mep_relaxer.py +199 -0
  23. firecode/modify_settings.py +186 -0
  24. firecode/mprof.py +65 -0
  25. firecode/multiembed.py +148 -0
  26. firecode/nci.py +186 -0
  27. firecode/numba_functions.py +260 -0
  28. firecode/operators.py +776 -0
  29. firecode/optimization_methods.py +609 -0
  30. firecode/parameters.py +84 -0
  31. firecode/pka.py +275 -0
  32. firecode/profiler.py +17 -0
  33. firecode/pruning.py +421 -0
  34. firecode/pt.py +32 -0
  35. firecode/quotes.json +6651 -0
  36. firecode/quotes.py +9 -0
  37. firecode/reactive_atoms_classes.py +666 -0
  38. firecode/references.py +11 -0
  39. firecode/rmsd.py +74 -0
  40. firecode/settings.py +75 -0
  41. firecode/solvents.py +126 -0
  42. firecode/tests/C2F2H4.xyz +10 -0
  43. firecode/tests/C2H4.xyz +8 -0
  44. firecode/tests/CH3Cl.xyz +7 -0
  45. firecode/tests/HCOOH.xyz +7 -0
  46. firecode/tests/HCOOOH.xyz +8 -0
  47. firecode/tests/chelotropic.txt +3 -0
  48. firecode/tests/cyclical.txt +3 -0
  49. firecode/tests/dihedral.txt +2 -0
  50. firecode/tests/string.txt +3 -0
  51. firecode/tests/trimolecular.txt +9 -0
  52. firecode/tests.py +151 -0
  53. firecode/torsion_module.py +1035 -0
  54. firecode/utils.py +541 -0
  55. firecode-1.0.0.dist-info/LICENSE +165 -0
  56. firecode-1.0.0.dist-info/METADATA +321 -0
  57. firecode-1.0.0.dist-info/RECORD +59 -0
  58. firecode-1.0.0.dist-info/WHEEL +5 -0
  59. 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