Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
146 changes: 98 additions & 48 deletions arc/job/adapters/ts/autotst_ts.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@
from arc.species.species import ARCSpecies, TSGuess, colliding_atoms

HAS_AUTOTST = True
try:
from autotst.reaction import Reaction as AutoTST_Reaction
except (ImportError, ModuleNotFoundError):
HAS_AUTOTST = False

if TYPE_CHECKING:
from arc.level import Level


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 @@ -218,9 +216,14 @@ def execute_incore(self):
"""
Execute a job incore.
"""
if not HAS_AUTOTST:
raise ModuleNotFoundError(f'Could not import AutoTST, make sure it is properly installed.\n'
f'See {self.url} for more information, or use the Makefile provided with ARC.')
# 1) Check that ARC knows *which* Python to use for AutoTST
if not AUTOTST_PYTHON:
raise ModuleNotFoundError(
"settings['AUTOTST_PYTHON'] is not set. "
"ARC cannot run AutoTST as a subprocess without this. "
"Set AUTOTST_PYTHON in your ARC settings to the Python executable of your tst_env."
)

self._log_job_execution()
self.initial_time = self.initial_time if self.initial_time else datetime.datetime.now()

Expand All @@ -234,83 +237,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