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/pka.py ADDED
@@ -0,0 +1,275 @@
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
+ import numpy as np
25
+
26
+ from firecode.calculators._xtb import xtb_get_free_energy
27
+ from firecode.torsion_module import csearch
28
+ from firecode.optimization_methods import _refine_structures, optimize, write_xyz
29
+ from firecode.utils import loadbar, graphize
30
+ from firecode.graph_manipulations import neighbors
31
+ from firecode.algebra import norm
32
+
33
+
34
+ def _get_anions(
35
+ embedder,
36
+ structures,
37
+ atomnos,
38
+ index,
39
+ logfunction=print,
40
+ ):
41
+ '''
42
+ structures: array of 3D of coordinates
43
+ atomnos: 1D array of atomic numbers
44
+ index: position of hydrogen atom to be abstracted
45
+
46
+ return: anion optimized geomertries, their energies and the new atomnos array
47
+ '''
48
+ assert embedder.options.calculator == 'XTB', 'Charge calculations not yet implemented for Gau, Orca, Mopac, OB'
49
+ # assert atomnos[index] == 1
50
+
51
+ atomnos = np.delete(atomnos, index)
52
+ # removing proton from atoms
53
+
54
+ solvent = embedder.options.solvent
55
+ if solvent is None:
56
+ logfunction('Solvent for pKa calculation not specified: defaulting to gas phase')
57
+
58
+ anions, energies = [], []
59
+
60
+ for s, structure in enumerate(structures):
61
+
62
+ coords = np.delete(structure, index, axis=0)
63
+ # new coordinates do not include the designated proton
64
+
65
+ print(f'Optimizing anion conformer {s+1}/{len(structures)} ...', end='\r')
66
+
67
+ opt_coords, energy, success = optimize(
68
+ coords,
69
+ atomnos,
70
+ calculator=embedder.options.calculator,
71
+ procs=embedder.procs,
72
+ solvent=solvent,
73
+ max_newbonds=embedder.options.max_newbonds,
74
+ title=f'temp_anion{s}',
75
+ check=True,
76
+ charge=-1,
77
+ )
78
+
79
+ if success:
80
+ anions.append(opt_coords)
81
+ energies.append(energy)
82
+
83
+ anions, energies = zip(*sorted(zip(anions, energies), key=lambda x: x[1]))
84
+
85
+ return anions, energies, atomnos
86
+
87
+ def _get_cations(
88
+ embedder,
89
+ structures,
90
+ atomnos,
91
+ index,
92
+ logfunction=print,
93
+ ):
94
+ '''
95
+ structures: array of 3D of coordinates
96
+ atomnos: 1D array of atomic numbers
97
+ index: position where the new hydrogen atom has to be inserted
98
+
99
+ return: cation optimized geomertries, their energies and the new atomnos array
100
+ '''
101
+ assert embedder.options.calculator == 'XTB', 'Charge calculations not yet implemented for Gau, Orca, Mopac, OB'
102
+
103
+ cation_atomnos = np.append(atomnos, 1)
104
+ # adding proton to atoms
105
+
106
+ solvent = embedder.options.solvent
107
+ if solvent is None:
108
+ logfunction('Solvent for pKa calculation not specified: defaulting to gas phase')
109
+
110
+ cations, energies = [], []
111
+
112
+ for s, structure in enumerate(structures):
113
+
114
+ coords = protonate(structure, atomnos, index)
115
+ # new coordinates which include an additional proton
116
+
117
+ print(f'Optimizing cation conformer {s+1}/{len(structures)} ...', end='\r')
118
+
119
+ opt_coords, energy, success = optimize(
120
+ coords,
121
+ cation_atomnos,
122
+ calculator=embedder.options.calculator,
123
+ procs=embedder.procs,
124
+ solvent=solvent,
125
+ max_newbonds=embedder.options.max_newbonds,
126
+ title=f'temp_cation{s}',
127
+ check=True,
128
+ charge=+1,
129
+ )
130
+
131
+ if success:
132
+ cations.append(opt_coords)
133
+ energies.append(energy)
134
+
135
+ cations, energies = zip(*sorted(zip(cations, energies), key=lambda x: x[1]))
136
+
137
+ return cations, energies, cation_atomnos
138
+
139
+ def protonate(coords, atomnos, index, length=1):
140
+ '''
141
+ Returns the input structure,
142
+ protonated at the index provided,
143
+ ready to be optimized
144
+ '''
145
+
146
+ graph = graphize(coords, atomnos)
147
+ nbs = neighbors(graph, index)
148
+ versor = -norm(np.mean(coords[nbs]-coords[index], axis=0))
149
+ new_proton_coords = coords[index] + length * versor
150
+ coords = np.append(coords, [new_proton_coords], axis=0)
151
+
152
+ return coords
153
+
154
+ def pka_routine(filename, embedder, search=True):
155
+ '''
156
+ Calculates the energy difference between
157
+ the most stable conformer of the provided
158
+ structure and its conjugate base, obtained
159
+ by removing one proton at the specified position.
160
+ '''
161
+ mol_index = [m.filename for m in embedder.objects].index(filename)
162
+ mol = embedder.objects[mol_index]
163
+
164
+ assert len(mol.reactive_indices) == 1, 'Please only specify one reactive atom for pKa calculations'
165
+
166
+ embedder.log(f'--> pKa computation protocol for {mol.filename}, index {mol.reactive_indices}')
167
+
168
+ if search:
169
+ if len(mol.atomcoords) > 1:
170
+ embedder.log(f'Using only the first molecule of {mol.filename} to generate conformers')
171
+
172
+ conformers = csearch(
173
+ mol.atomcoords[0],
174
+ mol.atomnos,
175
+ n_out=100,
176
+ mode=1,
177
+ logfunction=print,
178
+ interactive_print=True,
179
+ write_torsions=False,
180
+ title=mol.filename
181
+ )
182
+ else:
183
+ conformers = mol.atomcoords
184
+
185
+ conformers, _ =_refine_structures(
186
+ conformers,
187
+ mol.atomnos,
188
+ calculator=embedder.options.calculator,
189
+ method=embedder.options.theory_level,
190
+ procs=embedder.procs,
191
+ loadstring='Optimizing conformer'
192
+ )
193
+
194
+ embedder.log()
195
+
196
+ free_energies = get_free_energies(embedder, conformers, mol.atomnos, charge=0, title='Starting structure')
197
+ conformers, free_energies = zip(*sorted(zip(conformers, free_energies), key=lambda x: x[1]))
198
+
199
+ with open(f'{mol.rootname}_confs_opt.xyz', 'w') as f:
200
+
201
+ solvent_string = f', {embedder.options.solvent}' if embedder.options.solvent is not None else ''
202
+
203
+ for c, e in zip(conformers, free_energies):
204
+ write_xyz(c, mol.atomnos, f, title=f'G({embedder.options.theory_level}{solvent_string}, charge=0) = {round(e, 3)} kcal/mol')
205
+
206
+ if mol.atomnos[mol.reactive_indices[0]] == 1:
207
+ # we have an acid, form and optimize the anions
208
+
209
+ anions, _, anions_atomnos = _get_anions(
210
+ embedder,
211
+ conformers,
212
+ mol.atomnos,
213
+ mol.reactive_indices[0],
214
+ logfunction=embedder.log
215
+ )
216
+
217
+ anions_free_energies = get_free_energies(embedder, anions, anions_atomnos, charge=-1, title='Anion')
218
+ anions, anions_free_energies = zip(*sorted(zip(anions, anions_free_energies), key=lambda x: x[1]))
219
+
220
+ with open(f'{mol.rootname}_anions_opt.xyz', 'w') as f:
221
+ for c, e in zip(anions, anions_free_energies):
222
+ write_xyz(c, anions_atomnos, f, title=f'G({embedder.options.theory_level}{solvent_string}, charge=-1) = {round(e, 3)} kcal/mol')
223
+
224
+ e_HA = free_energies[0]
225
+ e_A = anions_free_energies[0]
226
+ embedder.objects[mol_index].pka_data = ('HA -> A-', e_A - e_HA)
227
+
228
+ embedder.log()
229
+
230
+ else:
231
+ # we have a base, form and optimize the cations
232
+
233
+ cations, _, cations_atomnos = _get_cations(
234
+ embedder,
235
+ conformers,
236
+ mol.atomnos,
237
+ mol.reactive_indices[0],
238
+ logfunction=embedder.log
239
+ )
240
+
241
+ cations_free_energies = get_free_energies(embedder, cations, cations_atomnos, charge=+1, title='Cation')
242
+ cations, cations_free_energies = zip(*sorted(zip(cations, cations_free_energies), key=lambda x: x[1]))
243
+
244
+ with open(f'{mol.rootname}_cations_opt.xyz', 'w') as f:
245
+ for c, e in zip(cations, cations_free_energies):
246
+ write_xyz(c, cations_atomnos, f, title=f'G({embedder.options.theory_level}{solvent_string}, charge=+1) = {round(e, 3)} kcal/mol')
247
+
248
+ e_B = free_energies[0]
249
+ e_BH = cations_free_energies[0]
250
+ embedder.objects[mol_index].pka_data = ('B -> BH+', e_BH - e_B)
251
+
252
+ embedder.log()
253
+
254
+ def get_free_energies(embedder, structures, atomnos, charge=0, title='Molecule'):
255
+ '''
256
+ '''
257
+ assert embedder.options.calculator == 'XTB', 'Free energy calculations not yet implemented for Gau, Orca, Mopac, OB'
258
+
259
+ free_energies = []
260
+
261
+ for s, structure in enumerate(structures):
262
+
263
+ loadbar(s, len(structures), f'{title} Hessian {s+1}/{len(structures)} ')
264
+
265
+ free_energies.append(xtb_get_free_energy(
266
+ structure,
267
+ atomnos,
268
+ method=embedder.options.theory_level,
269
+ solvent=embedder.options.solvent,
270
+ charge=charge,
271
+ ))
272
+
273
+ loadbar(len(structures), len(structures), f'{title} Hessian {len(structures)}/{len(structures)} ')
274
+
275
+ return free_energies
firecode/profiler.py ADDED
@@ -0,0 +1,17 @@
1
+ import cProfile
2
+ from pstats import Stats
3
+
4
+
5
+
6
+ def profiled_wrapper(filename, name):
7
+
8
+ datafile = f"firecode_{name}_cProfile.dat"
9
+ cProfile.run("Embedder(filename, args.name).run()", datafile)
10
+
11
+ with open(f"firecode_{name}_cProfile_output_time.txt", "w") as f:
12
+ p = Stats(datafile, stream=f)
13
+ p.sort_stats("time").print_stats()
14
+
15
+ with open(f"firecode_{name}_cProfile_output_cumtime.txt", "w") as f:
16
+ p = Stats(datafile, stream=f)
17
+ p.sort_stats("cumtime").print_stats()