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
|
@@ -0,0 +1,577 @@
|
|
|
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 firecode.settings import (CALCULATOR, DEFAULT_FF_LEVELS, FF_CALC,
|
|
24
|
+
FF_OPT_BOOL)
|
|
25
|
+
|
|
26
|
+
# Known keywords and relative priority level:
|
|
27
|
+
# 1 : First to run, set some option
|
|
28
|
+
# 2 : Second to run, modify variables, dependence on priority 1 options
|
|
29
|
+
# 3 : Third to run, modify variables, dependence on priority 2 options
|
|
30
|
+
|
|
31
|
+
keywords_dict = {
|
|
32
|
+
'BYPASS' : 1, # Debug keyword. Used to skip all pruning steps and
|
|
33
|
+
# directly output all the embedded geometries.
|
|
34
|
+
|
|
35
|
+
'CALC' : 1, # Manually overrides the calculator in "settings.py"
|
|
36
|
+
|
|
37
|
+
'CHARGE' : 1, # Specifies charge for the embedding
|
|
38
|
+
|
|
39
|
+
'CHECK' : 1, # Visualize the input molecules through the ASE GUI,
|
|
40
|
+
# to check orbital positions or reading faults.
|
|
41
|
+
|
|
42
|
+
'CONFS' : 1, # Maximum number of conformers generated by csearch
|
|
43
|
+
|
|
44
|
+
'CLASHES' : 1, # Manually specify the max number of clashes and/or
|
|
45
|
+
# the distance threshold at which two atoms are considered
|
|
46
|
+
# clashing. The more forgiving, the more structures will reach
|
|
47
|
+
# the geometry optimization step. Syntax: `CLASHES(num=3,dist=1.2)`
|
|
48
|
+
|
|
49
|
+
'CRESTNCI' : 1, # passes the "--nci" flag to CREST metadynamic conformational searches.
|
|
50
|
+
|
|
51
|
+
'DEEP' : 1, # Performs a deeper search, retaining more starting points
|
|
52
|
+
# for calculations and smaller turning angles.
|
|
53
|
+
|
|
54
|
+
'DEBUG' : 1, # DEBUG KEYWORD. Writes more stuff to file.
|
|
55
|
+
|
|
56
|
+
'DIST' : 2, # Manually imposed distance between specified atom pairs,
|
|
57
|
+
# in Angstroms. Syntax uses parenthesis and commas:
|
|
58
|
+
# `DIST(a=2.345,b=3.67,c=2.1)`
|
|
59
|
+
|
|
60
|
+
'DRYRUN' : 1, # skip any computing step - used to check for runtime errors during setup)
|
|
61
|
+
|
|
62
|
+
# 'ENANTIOMERS', # Do not discard enantiomeric structures.
|
|
63
|
+
|
|
64
|
+
'EZPROT' : 1, # Double bond protection
|
|
65
|
+
|
|
66
|
+
'FFOPT' : 1, #Manually turn on ``FF=ON`` or off ``FF=OFF`` the force
|
|
67
|
+
# field optimization step, overriding the value in ``settings.py``.
|
|
68
|
+
|
|
69
|
+
'FFCALC' : 1, # Manually overrides the force field calculator in "settings.py"
|
|
70
|
+
|
|
71
|
+
'FFLEVEL' : 1, # Manually set the theory level to be used.
|
|
72
|
+
# . Syntax: `FFLEVEL=UFF
|
|
73
|
+
|
|
74
|
+
'IMAGES' : 1, # Number of images to be used in NEB and mep_relax> jobs
|
|
75
|
+
|
|
76
|
+
'KCAL' : 1, # Trim output structures to a given value of relative energy.
|
|
77
|
+
# Syntax: `KCAL=n`, where n can be an integer or float.
|
|
78
|
+
|
|
79
|
+
'LET' : 1, # Overrides safety checks that prevent the
|
|
80
|
+
# program from running too large calculations
|
|
81
|
+
|
|
82
|
+
'LEVEL' : 1, # Manually set the theory level to be used.
|
|
83
|
+
# . Syntax: `LEVEL(PM7_EPS=6.15)
|
|
84
|
+
|
|
85
|
+
'MTD' : 1, # Run conformational augmentation through metadynamic sampling (XTB)
|
|
86
|
+
|
|
87
|
+
'NCI' : 1, # Estimate and print non-covalent interactions present in the generated poses.
|
|
88
|
+
|
|
89
|
+
'NEB' : 1, # Perform an automatical climbing image nudged elastic band (CI-NEB)
|
|
90
|
+
# TS search after the partial optimization step, inferring reagents
|
|
91
|
+
# and products for each generated TS pose. These are guessed by
|
|
92
|
+
# approaching the reactive atoms until they are at the right distance,
|
|
93
|
+
# and then partially constrained (reagents) or free (products) optimizations
|
|
94
|
+
# are carried out to get the start and end points for a CI-NEB TS search.
|
|
95
|
+
# For trimolecular transition states, only the first imposed pairing (a)
|
|
96
|
+
# is approached - i.e. the C-C reactive distance in the example above.
|
|
97
|
+
# This NEB option is only really usable for those reactions in which two
|
|
98
|
+
# (or three) molecules are bound together (or strongly interacting) after
|
|
99
|
+
# the TS, with no additional species involved. For example, cycloaddition
|
|
100
|
+
# reactions are great candidates while atom transfer reactions
|
|
101
|
+
# (i.e. epoxidations) are not. Of course this implementation is not
|
|
102
|
+
# always reliable, and it is provided more as an experimenting tool
|
|
103
|
+
# than a definitive feature.
|
|
104
|
+
|
|
105
|
+
'NEWBONDS' : 1, # Manually specify the maximum number of "new bonds" that a
|
|
106
|
+
# TS structure can have to be retained and not to be considered
|
|
107
|
+
# scrambled. Default is 1. Syntax: `NEWBONDS=1`
|
|
108
|
+
|
|
109
|
+
'NOOPT' : 1, # Skip the optimization steps, directly writing structures to file.
|
|
110
|
+
|
|
111
|
+
'ONLYREFINED' : 1, # Discard structures that do not successfully refine bonding distances.
|
|
112
|
+
|
|
113
|
+
'PKA' : 1, # Set reference pKa for a specific compound
|
|
114
|
+
|
|
115
|
+
'PROCS' : 1, # Set the number of parallel cores to be used by ORCA
|
|
116
|
+
|
|
117
|
+
'REFINE' : 1, # Same as calling refine> on a single file
|
|
118
|
+
|
|
119
|
+
'RIGID' : 1, # Does not apply to "string" embeds. Avoid
|
|
120
|
+
# bending structures to better build TSs.
|
|
121
|
+
|
|
122
|
+
'ROTRANGE' : 1, # Does not apply to "string" embeds. Manually specify the rotation
|
|
123
|
+
# range to be explored around the structure pivot.
|
|
124
|
+
# Default is 120. Syntax: `ROTRANGE=120`
|
|
125
|
+
|
|
126
|
+
'SADDLE' : 1, # After embed and refinement, optimize to first order saddle points
|
|
127
|
+
|
|
128
|
+
'SHRINK' : 1, # Exaggerate orbital dimensions during embed, scaling them by a factor
|
|
129
|
+
# of one and a half. This makes it easier to perform the embed without
|
|
130
|
+
# having molecules clashing one another. Then, the correct distance between
|
|
131
|
+
# reactive atom pairs is achieved as for standard runs by spring constraints
|
|
132
|
+
# during MOPAC/ORCA optimization.
|
|
133
|
+
|
|
134
|
+
'SIMPLEORBITALS' : 1, # Override the automatic orbital assignment, using "Single" type orbitals for
|
|
135
|
+
# every reactive atom
|
|
136
|
+
|
|
137
|
+
'SOLVENT' : 1, # set the solvation model
|
|
138
|
+
|
|
139
|
+
'STEPS' : 1, # Manually specify the number of steps to be taken in scanning rotations.
|
|
140
|
+
# For string embeds, the range to be explored is the full 360°, and the
|
|
141
|
+
# default `STEPS=24` will perform 15° turns. For cyclical and chelotropic
|
|
142
|
+
# embeds, the rotation range to be explored is +-`ROTRANGE` degrees.
|
|
143
|
+
# Therefore, the default value of `ROTRANGE=120 STEPS=12` will perform
|
|
144
|
+
# twelve 20 degrees turns.
|
|
145
|
+
|
|
146
|
+
'SUPRAFAC' : 1, # Only retain suprafacial orbital configurations in cyclical TSs.
|
|
147
|
+
# Thought for Diels-Alder and other cycloaddition reactions.
|
|
148
|
+
|
|
149
|
+
'RMSD' : 1, # RMSD threshold (Angstroms) for structure pruning. The smaller,
|
|
150
|
+
# the more retained structures. Default is 0.5 A.
|
|
151
|
+
# Syntax: `RMSD=n`, where n is a number.
|
|
152
|
+
|
|
153
|
+
'TS' : 1, # Uses various scans/saddle algorithms to locate the TS
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
def get_keyword_suggestion(unknown_kw):
|
|
157
|
+
'''
|
|
158
|
+
Given an unknown keyword, return the known one
|
|
159
|
+
with the highest score, if similar enough,
|
|
160
|
+
else return None.
|
|
161
|
+
'''
|
|
162
|
+
|
|
163
|
+
scores_tuples = [(kw, kw_similarity_score(kw, unknown_kw)) for kw in keywords_dict.keys()]
|
|
164
|
+
best_match, best_score = sorted(scores_tuples, key=lambda x: x[1], reverse=True)[0]
|
|
165
|
+
|
|
166
|
+
if best_score > 0.5:
|
|
167
|
+
return best_match
|
|
168
|
+
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
def kw_similarity_score(ref, kw):
|
|
172
|
+
'''
|
|
173
|
+
Simple scoring for string similarity
|
|
174
|
+
'''
|
|
175
|
+
|
|
176
|
+
score = 0
|
|
177
|
+
letters = []
|
|
178
|
+
for letter in kw:
|
|
179
|
+
if letter not in letters:
|
|
180
|
+
score += ref.count(letter)
|
|
181
|
+
letters.append(letter)
|
|
182
|
+
|
|
183
|
+
return score/len(ref)
|
|
184
|
+
|
|
185
|
+
class Truthy_struct:
|
|
186
|
+
def __bool__(self):
|
|
187
|
+
return True
|
|
188
|
+
|
|
189
|
+
class Options:
|
|
190
|
+
|
|
191
|
+
def __init__(self):
|
|
192
|
+
|
|
193
|
+
# only used by cyclical embeds, can be set here
|
|
194
|
+
self.rotation_range = 45
|
|
195
|
+
|
|
196
|
+
# Set later by the _setup() function based on embed type,
|
|
197
|
+
# since it is used by both cyclical and string embeds
|
|
198
|
+
self.rotation_steps = None
|
|
199
|
+
|
|
200
|
+
self.rmsd = 0.5
|
|
201
|
+
self.rigid = False
|
|
202
|
+
self.max_confs = 1000
|
|
203
|
+
|
|
204
|
+
self.max_clashes = 0
|
|
205
|
+
self.clash_thresh = 1.5
|
|
206
|
+
|
|
207
|
+
self.max_newbonds = 0
|
|
208
|
+
|
|
209
|
+
self.optimization = True
|
|
210
|
+
self.calculator = CALCULATOR
|
|
211
|
+
self.theory_level = None # set later in _calculator_setup()
|
|
212
|
+
self.solvent = None
|
|
213
|
+
self.charge = 0
|
|
214
|
+
self.ff_opt = FF_OPT_BOOL
|
|
215
|
+
self.ff_calc = FF_CALC
|
|
216
|
+
|
|
217
|
+
if self.ff_opt:
|
|
218
|
+
self.ff_level = DEFAULT_FF_LEVELS[FF_CALC]
|
|
219
|
+
|
|
220
|
+
self.neb = False
|
|
221
|
+
self.saddle = False
|
|
222
|
+
self.ts = False
|
|
223
|
+
self.nci = False
|
|
224
|
+
self.crestnci = False
|
|
225
|
+
self.shrink = False
|
|
226
|
+
self.shrink_multiplier = 1
|
|
227
|
+
self.metadynamics = False
|
|
228
|
+
self.suprafacial = False
|
|
229
|
+
self.simpleorbitals = False
|
|
230
|
+
self.only_refined = False
|
|
231
|
+
# self.keep_enantiomers = False
|
|
232
|
+
self.double_bond_protection = False
|
|
233
|
+
self.keep_hb = False
|
|
234
|
+
self.csearch_aug = False
|
|
235
|
+
self.dryrun = False
|
|
236
|
+
self.checkpoint_frequency = 50
|
|
237
|
+
|
|
238
|
+
self.fix_angles_in_deformation = False
|
|
239
|
+
# Not possible to set manually through a keyword.
|
|
240
|
+
# Monomolecular embeds have it on to prevent
|
|
241
|
+
# scrambling, but better to leave it off for
|
|
242
|
+
# less severe deformations, since convergence
|
|
243
|
+
# is faster
|
|
244
|
+
|
|
245
|
+
self.kcal_thresh = 10
|
|
246
|
+
self.bypass = False
|
|
247
|
+
self.debug = False
|
|
248
|
+
self.let = False
|
|
249
|
+
self.check_structures = False
|
|
250
|
+
self.noembed = False
|
|
251
|
+
# Default values, updated if _parse_input
|
|
252
|
+
# finds keywords and calls _set_options
|
|
253
|
+
|
|
254
|
+
self.operators = []
|
|
255
|
+
# this list will be filled with operator strings
|
|
256
|
+
# that need to be exectured before the run. i.e. ['csearch>mol.xyz']
|
|
257
|
+
|
|
258
|
+
self.operators_dict = {}
|
|
259
|
+
# Analogous dictionary that will contain the seuquences of operators for each molecule
|
|
260
|
+
|
|
261
|
+
def __repr__(self):
|
|
262
|
+
d = {var:self.__getattribute__(var) for var in dir(self) if var[0:2] != '__'}
|
|
263
|
+
|
|
264
|
+
repr_if_true = (
|
|
265
|
+
'bypass',
|
|
266
|
+
'check_structures',
|
|
267
|
+
'csearch_aug',
|
|
268
|
+
'crestnci',
|
|
269
|
+
'debug',
|
|
270
|
+
'let',
|
|
271
|
+
'metadynamics',
|
|
272
|
+
'nci',
|
|
273
|
+
'neb',
|
|
274
|
+
'saddle',
|
|
275
|
+
'ts',
|
|
276
|
+
'ff_opt',
|
|
277
|
+
'noembed',
|
|
278
|
+
'keep_hb',
|
|
279
|
+
'operators',
|
|
280
|
+
'keep_hb',
|
|
281
|
+
'dryrun',
|
|
282
|
+
'shrink',
|
|
283
|
+
'rigid',
|
|
284
|
+
'suprafacial',
|
|
285
|
+
'simpleorbitals',
|
|
286
|
+
'fix_angles_in_deformation',
|
|
287
|
+
'double_bond_protection',
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
for name in repr_if_true:
|
|
291
|
+
if not d.get(name, True):
|
|
292
|
+
d.pop(name)
|
|
293
|
+
|
|
294
|
+
repr_if_not_none = (
|
|
295
|
+
'kcal_thresh',
|
|
296
|
+
'solvent',
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
for name in repr_if_not_none:
|
|
300
|
+
if d[name] is None:
|
|
301
|
+
d.pop(name)
|
|
302
|
+
|
|
303
|
+
if not FF_OPT_BOOL:
|
|
304
|
+
d.pop('ff_calc')
|
|
305
|
+
|
|
306
|
+
padding = 1 + max([len(var) for var in d])
|
|
307
|
+
|
|
308
|
+
return '\n'.join([f'{var}{" "*(padding-len(var))}: {d[var]}' for var in d])
|
|
309
|
+
|
|
310
|
+
class OptionSetter:
|
|
311
|
+
|
|
312
|
+
def __init__(self, embedder, *args):
|
|
313
|
+
|
|
314
|
+
embedder.kw_line = embedder.kw_line if hasattr(embedder, 'kw_line') else ''
|
|
315
|
+
|
|
316
|
+
self.keywords = [word.split('=')[0].upper() if '(' not in word
|
|
317
|
+
else word.split('(')[0].upper()
|
|
318
|
+
for word in embedder.kw_line.split()]
|
|
319
|
+
|
|
320
|
+
self.keywords_simple = [k.upper() for k in embedder.kw_line.split()]
|
|
321
|
+
self.keywords_simple_case_sensitive = embedder.kw_line.split()
|
|
322
|
+
self.embedder = embedder
|
|
323
|
+
self.args = args
|
|
324
|
+
|
|
325
|
+
# if not all(k in keywords_dict.keys() for k in self.keywords):
|
|
326
|
+
for k in self.keywords:
|
|
327
|
+
if k not in keywords_dict.keys():
|
|
328
|
+
guess = get_keyword_suggestion(k)
|
|
329
|
+
extra = '' if guess is None else f' Did you mean \"{guess}\"?'
|
|
330
|
+
raise SyntaxError(f'Keyword \"{k}\" was not understood. Please check your syntax.{extra}')
|
|
331
|
+
|
|
332
|
+
if self.keywords_simple:
|
|
333
|
+
embedder.log('\n--> Parsed keywords, in order of execution:\n ' + ' '.join(self.sorted_keywords()) + '\n')
|
|
334
|
+
|
|
335
|
+
def refine(self, options, *args):
|
|
336
|
+
if len(self.embedder.objects) > 1:
|
|
337
|
+
raise SystemExit(('REFINE keyword can only be used with one multimolecular file per run, '
|
|
338
|
+
f'in .xyz format. ({len(self.embedder.objects)} files found in input)'))
|
|
339
|
+
|
|
340
|
+
options.noembed = True
|
|
341
|
+
|
|
342
|
+
def _refine_operator_routine(self):
|
|
343
|
+
if len(self.embedder.objects) > 1:
|
|
344
|
+
raise SystemExit(('The refine> operator can only be used with one multimolecular file per run, '
|
|
345
|
+
f'in .xyz format. ({len(self.embedder.objects)} files found in input)'))
|
|
346
|
+
|
|
347
|
+
self.embedder._set_embedder_structures_from_mol()
|
|
348
|
+
|
|
349
|
+
if self.embedder.options.rmsd is None:
|
|
350
|
+
# set this only if user did not already specify a value
|
|
351
|
+
self.embedder.options.rmsd = 0.25
|
|
352
|
+
|
|
353
|
+
self.embedder.objects[0].compute_orbitals(override='Single' if self.embedder.options.simpleorbitals else None)
|
|
354
|
+
|
|
355
|
+
def bypass(self, options, *args):
|
|
356
|
+
options.bypass = True
|
|
357
|
+
options.optimization = False
|
|
358
|
+
|
|
359
|
+
def charge(self, options, *args):
|
|
360
|
+
kw = self.keywords_simple[self.keywords.index('CHARGE')]
|
|
361
|
+
options.charge = int(kw.split('=')[1])
|
|
362
|
+
|
|
363
|
+
def confs(self, options, *args):
|
|
364
|
+
kw = self.keywords_simple[self.keywords.index('CONFS')]
|
|
365
|
+
options.max_confs = int(kw.split('=')[1])
|
|
366
|
+
|
|
367
|
+
def crestnci(self, options, *args):
|
|
368
|
+
options.crestnci = True
|
|
369
|
+
|
|
370
|
+
def dryrun(self, options, *args):
|
|
371
|
+
options.dryrun = True
|
|
372
|
+
|
|
373
|
+
def suprafac(self, options, *args):
|
|
374
|
+
options.suprafac = True
|
|
375
|
+
|
|
376
|
+
def deep(self, options, *args):
|
|
377
|
+
options.options.rmsd = 0.1
|
|
378
|
+
options.rotation_steps = 72
|
|
379
|
+
options.max_clashes = 1
|
|
380
|
+
options.clash_thresh = 1.4
|
|
381
|
+
|
|
382
|
+
def rotrange(self, options, *args):
|
|
383
|
+
kw = self.keywords_simple[self.keywords.index('ROTRANGE')]
|
|
384
|
+
options.rotation_range = int(kw.split('=')[1])
|
|
385
|
+
|
|
386
|
+
def steps(self, options, *args):
|
|
387
|
+
kw = self.keywords_simple[self.keywords.index('STEPS')]
|
|
388
|
+
options.custom_rotation_steps = int(kw.split('=')[1])
|
|
389
|
+
|
|
390
|
+
def rmsd(self, options, *args):
|
|
391
|
+
kw = self.keywords_simple[self.keywords.index('RMSD')]
|
|
392
|
+
options.rmsd = float(kw.split('=')[1])
|
|
393
|
+
|
|
394
|
+
def noopt(self, options, *args):
|
|
395
|
+
options.optimization = False
|
|
396
|
+
|
|
397
|
+
def ffopt(self, options, *args):
|
|
398
|
+
kw = self.keywords_simple[self.keywords.index('FFOPT')]
|
|
399
|
+
value = kw.split('=')[1].upper()
|
|
400
|
+
if value not in ('ON', 'OFF'):
|
|
401
|
+
raise SystemExit('FFOPT keyword can only have value \'ON\' or \'OFF\' (i.e. \'FFOPT=OFF\')')
|
|
402
|
+
|
|
403
|
+
options.ff_opt = True if value == 'ON' else False
|
|
404
|
+
|
|
405
|
+
def images(self, options, *args):
|
|
406
|
+
kw = self.keywords_simple[self.keywords.index('IMAGES')]
|
|
407
|
+
options.images = int(kw.split('=')[1])
|
|
408
|
+
|
|
409
|
+
def dist(self, options, *args):
|
|
410
|
+
kw = self.keywords_simple_case_sensitive[self.keywords.index('DIST')]
|
|
411
|
+
orb_string = kw[5:-1].replace(' ','')
|
|
412
|
+
# orb_string looks like 'a=2.345,b=3.456,c=2.22'
|
|
413
|
+
|
|
414
|
+
embedder = args[0]
|
|
415
|
+
embedder._set_custom_orbs(orb_string)
|
|
416
|
+
|
|
417
|
+
def clashes(self, options, *args):
|
|
418
|
+
kw = self.keywords_simple[self.keywords.index('CLASHES')]
|
|
419
|
+
clashes_string = kw[8:-1].lower().replace(' ','')
|
|
420
|
+
# clashes_string now looks like 'num=3,dist=1.2'
|
|
421
|
+
|
|
422
|
+
for piece in clashes_string.split(','):
|
|
423
|
+
s = piece.split('=')
|
|
424
|
+
if s[0].lower() == 'num':
|
|
425
|
+
options.max_clashes = int(s[1])
|
|
426
|
+
elif s[0].lower() == 'dist':
|
|
427
|
+
options.clash_thresh = float(s[1])
|
|
428
|
+
else:
|
|
429
|
+
raise SyntaxError((f'Syntax error in CLASHES keyword -> CLASHES({clashes_string}).' +
|
|
430
|
+
'Correct syntax looks like: CLASHES(num=3,dist=1.2)'))
|
|
431
|
+
|
|
432
|
+
def newbonds(self, options, *args):
|
|
433
|
+
kw = self.keywords_simple[self.keywords.index('NEWBONDS')]
|
|
434
|
+
options.max_newbonds = int(kw.split('=')[1])
|
|
435
|
+
|
|
436
|
+
def neb(self, options, *args):
|
|
437
|
+
options.neb = Truthy_struct()
|
|
438
|
+
options.neb.images = 6
|
|
439
|
+
options.neb.preopt = False
|
|
440
|
+
|
|
441
|
+
kw = self.keywords_simple[self.keywords.index('NEB')]
|
|
442
|
+
neb_options_string = kw[4:-1].lower().replace(' ','')
|
|
443
|
+
# neb_options_string now looks like 'images=8,preopt=true' or ''
|
|
444
|
+
|
|
445
|
+
if neb_options_string != '':
|
|
446
|
+
for piece in neb_options_string.split(','):
|
|
447
|
+
s = piece.split('=')
|
|
448
|
+
if s[0].lower() == 'images':
|
|
449
|
+
options.neb.images = int(s[1])
|
|
450
|
+
elif s[0].lower() == 'preopt':
|
|
451
|
+
if s[1].lower() == 'true':
|
|
452
|
+
options.neb.preopt = True
|
|
453
|
+
else:
|
|
454
|
+
raise SyntaxError((f'Syntax error in NEB keyword -> NEB({neb_options_string}). ' +
|
|
455
|
+
'Correct syntax looks like: NEB(images=8,preopt=true)'))
|
|
456
|
+
|
|
457
|
+
def level(self, options, *args):
|
|
458
|
+
kw = self.keywords_simple[self.keywords.index('LEVEL')]
|
|
459
|
+
options.theory_level = kw.split('=')[1].upper().replace('_', ' ')
|
|
460
|
+
|
|
461
|
+
options.theory_level = options.theory_level.replace('[', '(').replace(']', ')')
|
|
462
|
+
# quick fix for testing: allows the use of square brackets
|
|
463
|
+
# in place of round, so that the LEVEL keyword is not
|
|
464
|
+
# mistaken for one with sub-arguments. To be better addressed
|
|
465
|
+
# when/if a major rewrite of the option setting happens.
|
|
466
|
+
|
|
467
|
+
def fflevel(self, options, *args):
|
|
468
|
+
kw = self.keywords_simple[self.keywords.index('FFLEVEL')]
|
|
469
|
+
options.ff_level = kw.split('=')[1].upper().replace('_', ' ')
|
|
470
|
+
|
|
471
|
+
def rigid(self, options, *args):
|
|
472
|
+
options.rigid = True
|
|
473
|
+
|
|
474
|
+
def nci(self, options, *args):
|
|
475
|
+
options.nci = True
|
|
476
|
+
|
|
477
|
+
def onlyrefined(self, options, *args):
|
|
478
|
+
options.only_refined = True
|
|
479
|
+
|
|
480
|
+
def let(self, options, *args):
|
|
481
|
+
options.let = True
|
|
482
|
+
|
|
483
|
+
def check(self, options, *args):
|
|
484
|
+
options.check_structures = True
|
|
485
|
+
|
|
486
|
+
def simpleorbitals(self, options, *args):
|
|
487
|
+
options.simpleorbitals = True
|
|
488
|
+
|
|
489
|
+
def kcal(self, options, *args):
|
|
490
|
+
kw = self.keywords_simple[self.keywords.index('KCAL')]
|
|
491
|
+
options.kcal_thresh = float(kw.split('=')[1])
|
|
492
|
+
|
|
493
|
+
def shrink(self, options, *args):
|
|
494
|
+
options.shrink = True
|
|
495
|
+
kw = self.keywords_simple[self.keywords.index('SHRINK')]
|
|
496
|
+
|
|
497
|
+
parsed = kw.split('=')
|
|
498
|
+
options.shrink_multiplier = float(parsed[1]) if len(parsed) > 1 else 1.5
|
|
499
|
+
|
|
500
|
+
# def enantiomers(self, options, *args):
|
|
501
|
+
# options.keep_enantiomers = True
|
|
502
|
+
|
|
503
|
+
def debug(self, options, *args):
|
|
504
|
+
options.debug = True
|
|
505
|
+
|
|
506
|
+
# open a dedicated debug logfile
|
|
507
|
+
debug_log_filename = f'firecode_{self.embedder.stamp}_debug.log'
|
|
508
|
+
self.embedder.debug_logfile = open(debug_log_filename, 'a', buffering=1, encoding="utf-8")
|
|
509
|
+
|
|
510
|
+
from logging import basicConfig
|
|
511
|
+
basicConfig(filename=debug_log_filename, filemode='a')
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def procs(self, options, *args):
|
|
515
|
+
kw = self.keywords_simple[self.keywords.index('PROCS')]
|
|
516
|
+
self.embedder.procs = int(kw.split('=')[1])
|
|
517
|
+
|
|
518
|
+
def ezprot(self, options, *args):
|
|
519
|
+
options.double_bond_protection = True
|
|
520
|
+
|
|
521
|
+
def calc(self, options, *args):
|
|
522
|
+
kw = self.keywords_simple[self.keywords.index('CALC')]
|
|
523
|
+
options.calculator = kw.split('=')[1]
|
|
524
|
+
|
|
525
|
+
def ffcalc(self, options, *args):
|
|
526
|
+
kw = self.keywords_simple[self.keywords.index('FFCALC')]
|
|
527
|
+
options.ff_calc = kw.split('=')[1]
|
|
528
|
+
|
|
529
|
+
def mtd(self, options, *args):
|
|
530
|
+
if options.calculator != 'XTB':
|
|
531
|
+
raise SystemExit(('Metadynamics augmentation can only be run with the XTB calculator.\n'
|
|
532
|
+
'Change it in settings.py or use the CALC=XTB keyword.\n'))
|
|
533
|
+
options.metadynamics = True
|
|
534
|
+
|
|
535
|
+
def saddle(self, options, *args):
|
|
536
|
+
if not options.optimization:
|
|
537
|
+
raise SystemExit('SADDLE keyword can only be used if optimization is turned on. (Not compatible with NOOPT).')
|
|
538
|
+
options.saddle = True
|
|
539
|
+
|
|
540
|
+
def solvent(self, options, *args):
|
|
541
|
+
from firecode.solvents import solvent_synonyms
|
|
542
|
+
kw = self.keywords_simple[self.keywords.index('SOLVENT')]
|
|
543
|
+
solvent = kw.split('=')[1].lower()
|
|
544
|
+
options.solvent = solvent_synonyms.get(solvent, solvent)
|
|
545
|
+
|
|
546
|
+
def pka(self, options, *args):
|
|
547
|
+
kw = self.keywords_simple_case_sensitive[self.keywords.index('PKA')]
|
|
548
|
+
pka_string, pka = kw.split('=')
|
|
549
|
+
molname = pka_string[4:-1].replace(' ','')
|
|
550
|
+
|
|
551
|
+
if molname in [mol.filename for mol in self.embedder.objects]:
|
|
552
|
+
if any([f'pka>{molname}' in op.replace(' ', '') for op in self.embedder.options.operators]):
|
|
553
|
+
self.embedder.pka_ref = (molname, float(pka))
|
|
554
|
+
return
|
|
555
|
+
|
|
556
|
+
raise SyntaxError(f'{molname} must be present in the molecule lines, along with the pka> operator. Syntax: pka(mol.xyz)=n')
|
|
557
|
+
|
|
558
|
+
def csearch(self, options, *args):
|
|
559
|
+
options.csearch_aug = True
|
|
560
|
+
|
|
561
|
+
def set_options(self):
|
|
562
|
+
|
|
563
|
+
# self.keywords = sorted(self.keywords, key=lambda x: __keywords__.index(x))
|
|
564
|
+
|
|
565
|
+
for kw in self.sorted_keywords():
|
|
566
|
+
setter_function = getattr(self, kw.lower())
|
|
567
|
+
setter_function(self.embedder.options, self.embedder, *self.args)
|
|
568
|
+
|
|
569
|
+
if any('refine>' in op for op in self.embedder.options.operators) or self.embedder.options.noembed:
|
|
570
|
+
self._refine_operator_routine()
|
|
571
|
+
|
|
572
|
+
def sorted_keywords(self):
|
|
573
|
+
'''
|
|
574
|
+
Returns all the keywords sorted in the optimal execution order.
|
|
575
|
+
|
|
576
|
+
'''
|
|
577
|
+
return sorted(self.keywords, key=keywords_dict.get)
|