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/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()
|