Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,4 @@ build/*

*.log
*.xml
AGENTS.md
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ DEVTOOLS_DIR := devtools

.PHONY: all help clean test test-unittests test-functional test-all \
install-all install-ci install-pyrdl install-rmgdb install-autotst install-gcn \
install-gcn-cpu install-kinbot install-sella install-xtb install-torchani install-ob \
install-gcn-cpu install-kinbot install-sella install-xtb install-crest install-torchani install-ob \
lite check-env compile


Expand All @@ -35,6 +35,7 @@ help:
@echo " install-kinbot Install KinBot"
@echo " install-sella Install Sella"
@echo " install-xtb Install xTB"
@echo " install-crest Install CREST"
@echo " install-torchani Install TorchANI"
@echo " install-ob Install OpenBabel"
@echo ""
Expand Down Expand Up @@ -96,6 +97,9 @@ install-sella:
install-xtb:
bash $(DEVTOOLS_DIR)/install_xtb.sh

install-crest:
bash $(DEVTOOLS_DIR)/install_crest.sh

install-torchani:
bash $(DEVTOOLS_DIR)/install_torchani.sh

Expand Down
1 change: 1 addition & 0 deletions arc/job/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ class JobEnum(str, Enum):
# TS search methods
autotst = 'autotst' # AutoTST, 10.1021/acs.jpca.7b07361, 10.26434/chemrxiv.13277870.v2
heuristics = 'heuristics' # ARC's heuristics
crest = 'crest' # CREST conformer/TS search
kinbot = 'kinbot' # KinBot, 10.1016/j.cpc.2019.106947
gcn = 'gcn' # Graph neural network for isomerization, https://doi.org/10.1021/acs.jpclett.0c00500
user = 'user' # user guesses
Expand Down
5 changes: 3 additions & 2 deletions arc/job/adapters/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
'Cyclic_Ether_Formation': ['kinbot'],
'Cyclopentadiene_scission': ['gcn', 'xtb_gsm'],
'Diels_alder_addition': ['kinbot'],
'H_Abstraction': ['heuristics', 'autotst'],
'H_Abstraction': ['heuristics', 'autotst', 'crest'],
'carbonyl_based_hydrolysis': ['heuristics'],
'ether_hydrolysis': ['heuristics'],
'nitrile_hydrolysis': ['heuristics'],
Expand Down Expand Up @@ -77,7 +77,8 @@
adapters_that_do_not_require_a_level_arg = ['xtb', 'torchani']

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


def _initialize_adapter(obj: 'JobAdapter',
Expand Down
1 change: 1 addition & 0 deletions arc/job/adapters/ts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import arc.job.adapters.ts.autotst_ts
import arc.job.adapters.ts.crest
import arc.job.adapters.ts.gcn_ts
import arc.job.adapters.ts.heuristics
import arc.job.adapters.ts.kinbot_ts
Expand Down
131 changes: 90 additions & 41 deletions arc/job/adapters/ts/autotst_ts.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@


AUTOTST_PYTHON = settings['AUTOTST_PYTHON']
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic for setting HAS_AUTOTST is incomplete. Line 22 sets HAS_AUTOTST = True unconditionally, then lines 29-30 may set it to False if AUTOTST_PYTHON is None. However, there's no check if AUTOTST_PYTHON is defined in settings. If settings doesn't have 'AUTOTST_PYTHON', this will raise a KeyError. Consider wrapping AUTOTST_PYTHON retrieval in a try-except block or using settings.get('AUTOTST_PYTHON').

Suggested change
AUTOTST_PYTHON = settings['AUTOTST_PYTHON']
AUTOTST_PYTHON = settings.get('AUTOTST_PYTHON')

Copilot uses AI. Check for mistakes.
if AUTOTST_PYTHON is None:
HAS_AUTOTST = False

logger = get_logger()

Expand Down Expand Up @@ -229,83 +231,130 @@ def execute_incore(self):
charge=rxn.charge,
multiplicity=rxn.multiplicity,
)

reaction_label_fwd = get_autotst_reaction_string(rxn)
reaction_label_rev = get_autotst_reaction_string(ARCReaction(r_species=rxn.p_species,
p_species=rxn.r_species,
reactants=rxn.products,
products=rxn.reactants))
reaction_label_rev = get_autotst_reaction_string(
ARCReaction(
r_species=rxn.p_species,
p_species=rxn.r_species,
reactants=rxn.products,
products=rxn.reactants,
)
)

i = 0
for reaction_label, direction in zip([reaction_label_fwd, reaction_label_rev], ['F', 'R']):
# run AutoTST as a subprocess in the desired direction
script_path = os.path.join(ARC_PATH, 'arc', 'job', 'adapters', 'scripts', 'autotst_script.py')
commands = ['source ~/.bashrc', f'"{AUTOTST_PYTHON}" "{script_path}" "{reaction_label}" "{self.output_path}"']
for reaction_label, direction in zip(
[reaction_label_fwd, reaction_label_rev],
['F', 'R'],
):
script_path = os.path.join(
ARC_PATH, 'arc', 'job', 'adapters', 'scripts', 'autotst_script.py'
)
# 2) Build the bash command to run tst_env’s Python on the script
commands = [
'source ~/.bashrc',
f'"{AUTOTST_PYTHON}" "{script_path}" "{reaction_label}" "{self.output_path}"',
]
command = '; '.join(commands)

tic = datetime.datetime.now()

output = subprocess.run(command, shell=True, executable='/bin/bash')
# 3) Capture stdout/stderr so we can diagnose missing AutoTST
output = subprocess.run(
command,
shell=True,
executable='/bin/bash',
capture_output=True,
text=True,
)

tok = datetime.datetime.now() - tic

if output.returncode:
direction_str = 'forward' if direction == 'F' else 'reverse'
logger.warning(f'AutoTST subprocess did not give a successful return code for {rxn} '
f'in the {direction_str} direction.\n'
f'Got return code: {output.returncode}\n'
f'stdout: {output.stdout}\n'
f'stderr: {output.stderr}')
stderr = output.stderr or ""
stdout = output.stdout or ""

# Special case: autotst itself is missing in tst_env
if 'No module named' in stderr and 'autotst' in stderr:
logger.error(
f"AutoTST subprocess failed for {rxn} because the 'autotst' "
f"package is not importable in the tst_env used by AUTOTST_PYTHON:\n"
f"{stderr}"
)
else:
direction_str = 'forward' if direction == 'F' else 'reverse'
logger.warning(
f'AutoTST subprocess did not give a successful return code for {rxn} '
f'in the {direction_str} direction.\n'
f'Got return code: {output.returncode}\n'
f'stdout: {stdout}\n'
f'stderr: {stderr}'
)

# 4) Check for the YAML output and add TS guesses as before
if os.path.isfile(self.output_path):
results = read_yaml_file(path=self.output_path)
if results:
for result in results:
xyz = xyz_from_data(coords=result['coords'], numbers=result['numbers'])
xyz = xyz_from_data(
coords=result['coords'],
numbers=result['numbers'],
)
unique = True
for other_tsg in rxn.ts_species.ts_guesses:
if other_tsg.success and almost_equal_coords(xyz, other_tsg.initial_xyz):
if other_tsg.success and almost_equal_coords(
xyz, other_tsg.initial_xyz
):
if 'autotst' not in other_tsg.method.lower():
other_tsg.method += ' and AutoTST'
unique = False
break
if unique and not colliding_atoms(xyz):
ts_guess = TSGuess(method='AutoTST',
method_direction=direction,
method_index=i,
t0=tic,
execution_time=tok,
xyz=xyz,
success=True,
index=len(rxn.ts_species.ts_guesses),
)
ts_guess = TSGuess(
method='AutoTST',
method_direction=direction,
method_index=i,
t0=tic,
execution_time=tok,
xyz=xyz,
success=True,
index=len(rxn.ts_species.ts_guesses),
)
rxn.ts_species.ts_guesses.append(ts_guess)
save_geo(xyz=xyz,
path=self.local_path,
filename=f'AutoTST {direction}',
format_='xyz',
comment=f'AutoTST {direction}',
)
save_geo(
xyz=xyz,
path=self.local_path,
filename=f'AutoTST {direction}',
format_='xyz',
comment=f'AutoTST {direction}',
)
i += 1
else:
ts_guess = TSGuess(method=f'AutoTST',
method_direction=direction,
method_index=i,
t0=tic,
execution_time=tok,
success=False,
index=len(rxn.ts_species.ts_guesses),
)
ts_guess = TSGuess(
method='AutoTST',
method_direction=direction,
method_index=i,
t0=tic,
execution_time=tok,
success=False,
index=len(rxn.ts_species.ts_guesses),
)
rxn.ts_species.ts_guesses.append(ts_guess)
i += 1

if len(self.reactions) < 5:
successes = len([tsg for tsg in rxn.ts_species.ts_guesses if tsg.success and 'autotst' in tsg.method])
successes = len(
[tsg for tsg in rxn.ts_species.ts_guesses
if tsg.success and 'autotst' in tsg.method.lower()]
)
if successes:
logger.info(f'AutoTST successfully found {successes} TS guesses for {rxn.label}.')
else:
logger.info(f'AutoTST did not find any successful TS guesses for {rxn.label}.')

self.final_time = datetime.datetime.now()


def execute_queue(self):
"""
(Execute a job to the server's queue.)
Expand Down
Loading
Loading