Skip to content

Commit 96b4150

Browse files
committed
updates
1 parent 7730a26 commit 96b4150

6 files changed

Lines changed: 246 additions & 68 deletions

File tree

arc/job/adapter.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class JobEnum(str, Enum):
9797
# TS search methods
9898
autotst = 'autotst' # AutoTST, 10.1021/acs.jpca.7b07361, 10.26434/chemrxiv.13277870.v2
9999
heuristics = 'heuristics' # ARC's heuristics
100+
crest = 'crest' # CREST conformer/TS search
100101
kinbot = 'kinbot' # KinBot, 10.1016/j.cpc.2019.106947
101102
gcn = 'gcn' # Graph neural network for isomerization, https://doi.org/10.1021/acs.jpclett.0c00500
102103
user = 'user' # user guesses

arc/job/adapters/common.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
'Cyclic_Ether_Formation': ['kinbot'],
4242
'Cyclopentadiene_scission': ['gcn', 'xtb_gsm'],
4343
'Diels_alder_addition': ['kinbot'],
44-
'H_Abstraction': ['heuristics', 'autotst'],
44+
'H_Abstraction': ['heuristics', 'autotst', 'crest'],
4545
'carbonyl_based_hydrolysis': ['heuristics'],
4646
'ether_hydrolysis': ['heuristics'],
4747
'nitrile_hydrolysis': ['heuristics'],
@@ -77,7 +77,8 @@
7777
adapters_that_do_not_require_a_level_arg = ['xtb', 'torchani']
7878

7979
# Default is "queue", "pipe" will be called whenever needed. So just list 'incore'.
80-
default_incore_adapters = ['autotst', 'gcn', 'heuristics', 'kinbot', 'psi4', 'xtb', 'xtb_gsm', 'torchani', 'openbabel']
80+
default_incore_adapters = ['autotst', 'crest', 'gcn', 'heuristics', 'kinbot', 'psi4', 'xtb', 'xtb_gsm', 'torchani',
81+
'openbabel']
8182

8283

8384
def _initialize_adapter(obj: 'JobAdapter',

arc/job/adapters/ts/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import arc.job.adapters.ts.autotst_ts
2+
import arc.job.adapters.ts.crest
23
import arc.job.adapters.ts.gcn_ts
34
import arc.job.adapters.ts.heuristics
45
import arc.job.adapters.ts.kinbot_ts

arc/job/adapters/ts/crest.py

Lines changed: 239 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,29 @@
44
Separated from heuristics so CREST can be conditionally imported and reused.
55
"""
66

7+
import datetime
78
import os
89
import re
910
import time
10-
from typing import List
11+
from typing import TYPE_CHECKING, List, Optional, Union
1112

1213
import numpy as np
1314
import pandas as pd
1415

15-
from arc.common import get_logger
16+
from arc.common import almost_equal_coords, get_logger
1617
from arc.imports import settings, submit_scripts
18+
from arc.job.adapter import JobAdapter
19+
from arc.job.adapters.common import _initialize_adapter, ts_adapters_by_rmg_family
20+
from arc.job.factory import register_job_adapter
1721
from arc.job.local import check_job_status, submit_job
22+
from arc.plotter import save_geo
1823
from arc.species.converter import reorder_xyz_string, str_to_xyz, xyz_to_dmat, xyz_to_str
24+
from arc.species.species import ARCSpecies, TSGuess
25+
from arc.job.adapters.ts.heuristics import h_abstraction
26+
27+
if TYPE_CHECKING:
28+
from arc.level import Level
29+
from arc.reaction import ARCReaction
1930

2031
logger = get_logger()
2132

@@ -38,6 +49,229 @@ def crest_available() -> bool:
3849
return bool(SERVERS.get("local")) and bool(CREST_PATH or CREST_ENV_PATH)
3950

4051

52+
class CrestAdapter(JobAdapter):
53+
"""
54+
A class for executing CREST TS conformer searches based on heuristics-generated guesses.
55+
"""
56+
57+
def __init__(self,
58+
project: str,
59+
project_directory: str,
60+
job_type: Union[List[str], str],
61+
args: Optional[dict] = None,
62+
bath_gas: Optional[str] = None,
63+
checkfile: Optional[str] = None,
64+
conformer: Optional[int] = None,
65+
constraints: Optional[List] = None,
66+
cpu_cores: Optional[str] = None,
67+
dihedral_increment: Optional[float] = None,
68+
dihedrals: Optional[List[float]] = None,
69+
directed_scan_type: Optional[str] = None,
70+
ess_settings: Optional[dict] = None,
71+
ess_trsh_methods: Optional[List[str]] = None,
72+
execution_type: Optional[str] = None,
73+
fine: bool = False,
74+
initial_time: Optional[Union['datetime.datetime', str]] = None,
75+
irc_direction: Optional[str] = None,
76+
job_id: Optional[int] = None,
77+
job_memory_gb: float = 14.0,
78+
job_name: Optional[str] = None,
79+
job_num: Optional[int] = None,
80+
job_server_name: Optional[str] = None,
81+
job_status: Optional[List[Union[dict, str]]] = None,
82+
level: Optional['Level'] = None,
83+
max_job_time: Optional[float] = None,
84+
run_multi_species: bool = False,
85+
reactions: Optional[List['ARCReaction']] = None,
86+
rotor_index: Optional[int] = None,
87+
server: Optional[str] = None,
88+
server_nodes: Optional[list] = None,
89+
queue: Optional[str] = None,
90+
attempted_queues: Optional[List[str]] = None,
91+
species: Optional[List[ARCSpecies]] = None,
92+
testing: bool = False,
93+
times_rerun: int = 0,
94+
torsions: Optional[List[List[int]]] = None,
95+
tsg: Optional[int] = None,
96+
xyz: Optional[dict] = None,
97+
):
98+
99+
self.incore_capacity = 50
100+
self.job_adapter = 'crest'
101+
self.command = None
102+
self.execution_type = execution_type or 'incore'
103+
104+
if reactions is None:
105+
raise ValueError('Cannot execute TS CREST without ARCReaction object(s).')
106+
107+
dihedral_increment = dihedral_increment or 30
108+
109+
_initialize_adapter(obj=self,
110+
is_ts=True,
111+
project=project,
112+
project_directory=project_directory,
113+
job_type=job_type,
114+
args=args,
115+
bath_gas=bath_gas,
116+
checkfile=checkfile,
117+
conformer=conformer,
118+
constraints=constraints,
119+
cpu_cores=cpu_cores,
120+
dihedral_increment=dihedral_increment,
121+
dihedrals=dihedrals,
122+
directed_scan_type=directed_scan_type,
123+
ess_settings=ess_settings,
124+
ess_trsh_methods=ess_trsh_methods,
125+
fine=fine,
126+
initial_time=initial_time,
127+
irc_direction=irc_direction,
128+
job_id=job_id,
129+
job_memory_gb=job_memory_gb,
130+
job_name=job_name,
131+
job_num=job_num,
132+
job_server_name=job_server_name,
133+
job_status=job_status,
134+
level=level,
135+
max_job_time=max_job_time,
136+
run_multi_species=run_multi_species,
137+
reactions=reactions,
138+
rotor_index=rotor_index,
139+
server=server,
140+
server_nodes=server_nodes,
141+
queue=queue,
142+
attempted_queues=attempted_queues,
143+
species=species,
144+
testing=testing,
145+
times_rerun=times_rerun,
146+
torsions=torsions,
147+
tsg=tsg,
148+
xyz=xyz,
149+
)
150+
151+
def write_input_file(self) -> None:
152+
pass
153+
154+
def set_files(self) -> None:
155+
pass
156+
157+
def set_additional_file_paths(self) -> None:
158+
pass
159+
160+
def set_input_file_memory(self) -> None:
161+
pass
162+
163+
def execute_incore(self):
164+
self._log_job_execution()
165+
self.initial_time = self.initial_time if self.initial_time else datetime.datetime.now()
166+
167+
supported_families = [key for key, val in ts_adapters_by_rmg_family.items() if 'crest' in val]
168+
169+
self.reactions = [self.reactions] if not isinstance(self.reactions, list) else self.reactions
170+
for rxn in self.reactions:
171+
if rxn.family not in supported_families:
172+
logger.warning(f'The CREST TS search adapter does not support the {rxn.family} reaction family.')
173+
continue
174+
if any(spc.get_xyz() is None for spc in rxn.r_species + rxn.p_species):
175+
logger.warning(f'The CREST TS search adapter cannot process a reaction if 3D coordinates of '
176+
f'some/all of its reactants/products are missing.\nNot processing {rxn}.')
177+
continue
178+
if not crest_available():
179+
logger.warning('CREST is not available. Skipping CREST TS search.')
180+
continue
181+
182+
if rxn.ts_species is None:
183+
rxn.ts_species = ARCSpecies(label='TS',
184+
is_ts=True,
185+
charge=rxn.charge,
186+
multiplicity=rxn.multiplicity,
187+
)
188+
189+
tsg = TSGuess(method='CREST')
190+
tsg.tic()
191+
192+
crest_job_dirs = []
193+
xyz_guesses = h_abstraction(reaction=rxn,
194+
dihedral_increment=self.dihedral_increment,
195+
path=self.local_path)
196+
if not xyz_guesses:
197+
logger.warning(f'CREST TS search failed to generate any seed guesses for {rxn.label}.')
198+
tsg.tok()
199+
continue
200+
201+
for iteration, xyz_entry in enumerate(xyz_guesses):
202+
if isinstance(xyz_entry, dict):
203+
xyz_guess = xyz_entry.get("xyz")
204+
else:
205+
xyz_guess = xyz_entry
206+
if xyz_guess is None:
207+
continue
208+
209+
df_dmat = convert_xyz_to_df(xyz_guess)
210+
try:
211+
h_abs_atoms_dict = get_h_abs_atoms(df_dmat)
212+
crest_job_dir = crest_ts_conformer_search(
213+
xyz_guess,
214+
h_abs_atoms_dict["A"],
215+
h_abs_atoms_dict["H"],
216+
h_abs_atoms_dict["B"],
217+
path=self.local_path,
218+
xyz_crest_int=iteration,
219+
)
220+
crest_job_dirs.append(crest_job_dir)
221+
except (ValueError, KeyError) as e:
222+
logger.error(f"Could not determine the H abstraction atoms, got:\n{e}")
223+
224+
if not crest_job_dirs:
225+
logger.warning(f'CREST TS search failed to prepare any jobs for {rxn.label}.')
226+
tsg.tok()
227+
continue
228+
229+
crest_jobs = submit_crest_jobs(crest_job_dirs)
230+
monitor_crest_jobs(crest_jobs)
231+
xyz_guesses_crest = process_completed_jobs(crest_jobs)
232+
tsg.tok()
233+
234+
for method_index, xyz in enumerate(xyz_guesses_crest):
235+
if xyz is None:
236+
continue
237+
unique = True
238+
for other_tsg in rxn.ts_species.ts_guesses:
239+
if almost_equal_coords(xyz, other_tsg.initial_xyz):
240+
if 'crest' not in other_tsg.method.lower():
241+
other_tsg.method += ' and CREST'
242+
unique = False
243+
break
244+
if unique:
245+
ts_guess = TSGuess(method='CREST',
246+
index=len(rxn.ts_species.ts_guesses),
247+
method_index=method_index,
248+
t0=tsg.t0,
249+
execution_time=tsg.execution_time,
250+
success=True,
251+
family=rxn.family,
252+
xyz=xyz,
253+
)
254+
rxn.ts_species.ts_guesses.append(ts_guess)
255+
save_geo(xyz=xyz,
256+
path=self.local_path,
257+
filename=f'CREST_{method_index}',
258+
format_='xyz',
259+
comment=f'CREST {method_index}, family: {rxn.family}',
260+
)
261+
262+
if len(self.reactions) < 5:
263+
successes = [tsg for tsg in rxn.ts_species.ts_guesses if tsg.success and 'crest' in tsg.method.lower()]
264+
if successes:
265+
logger.info(f'CREST successfully found {len(successes)} TS guesses for {rxn.label}.')
266+
else:
267+
logger.info(f'CREST did not find any successful TS guesses for {rxn.label}.')
268+
269+
self.final_time = datetime.datetime.now()
270+
271+
def execute_queue(self):
272+
self.execute_incore()
273+
274+
41275
def crest_ts_conformer_search(
42276
xyz_guess: dict,
43277
a_atom: int,
@@ -376,3 +610,6 @@ def get_h_abs_atoms(dataframe: pd.DataFrame) -> dict:
376610
}
377611
else:
378612
raise ValueError("No valid hydrogen atom found.")
613+
614+
615+
register_job_adapter('crest', CrestAdapter)

0 commit comments

Comments
 (0)