Skip to content

Commit 40ed4f2

Browse files
Implemented requested changes and updated tests
Signed-off-by: Jonathan Schöps <s6jtscho@uni-bonn.de>
1 parent f29fe5c commit 40ed4f2

2 files changed

Lines changed: 59 additions & 134 deletions

File tree

src/mindlessgen/qm/orca.py

Lines changed: 47 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@ def __init__(
3232
raise TypeError("orca_path should be a string or a Path object.")
3333
self.cfg = orcacfg
3434
self.xtb_cfg = xtb_config
35-
self.xtb_driver_enabled = bool(xtb_config) and bool(
36-
getattr(self.cfg, "use_xtb_driver", False)
37-
)
3835
# must be explicitly initialized in current parallelization implementation
3936
# as accessing parent class variables might not be possible
4037
self.tmp_dir = self.__class__.get_temporary_directory()
@@ -61,7 +58,7 @@ def optimize(
6158
xyz_filename = "molecule.xyz"
6259
molecule.write_xyz_to_file(temp_path / xyz_filename)
6360

64-
if self._should_use_xtb_driver():
61+
if self.cfg.use_xtb_driver:
6562
optimized_molecule = self.optimize_xtb_driver(
6663
temp_path=temp_path,
6764
molecule=molecule,
@@ -70,39 +67,39 @@ def optimize(
7067
max_cycles=max_cycles,
7168
verbosity=verbosity,
7269
)
73-
else:
74-
inputname = "orca_opt.inp"
75-
orca_input = self._gen_input(
76-
molecule,
77-
xyz_filename,
78-
temp_path,
79-
ncores,
80-
True,
81-
max_cycles,
82-
)
83-
if verbosity > 1:
84-
print("ORCA input file:\n##################")
85-
print(orca_input)
86-
print("##################")
87-
with open(temp_path / inputname, "w", encoding="utf8") as f:
88-
f.write(orca_input)
89-
# run orca
90-
arguments = [
91-
inputname,
92-
]
93-
orca_log_out, orca_log_err, return_code = self._run(
94-
temp_path=temp_path, arguments=arguments
70+
return optimized_molecule
71+
inputname = "orca_opt.inp"
72+
orca_input = self._gen_input(
73+
molecule,
74+
xyz_filename,
75+
temp_path,
76+
ncores,
77+
True,
78+
max_cycles,
79+
)
80+
if verbosity > 1:
81+
print("ORCA input file:\n##################")
82+
print(orca_input)
83+
print("##################")
84+
with open(temp_path / inputname, "w", encoding="utf8") as f:
85+
f.write(orca_input)
86+
# run orca
87+
arguments = [
88+
inputname,
89+
]
90+
orca_log_out, orca_log_err, return_code = self._run(
91+
temp_path=temp_path, arguments=arguments
92+
)
93+
if verbosity > 2:
94+
print(orca_log_out)
95+
if return_code != 0:
96+
raise RuntimeError(
97+
f"ORCA failed with return code {return_code}:\n{orca_log_err}"
9598
)
96-
if verbosity > 2:
97-
print(orca_log_out)
98-
if return_code != 0:
99-
raise RuntimeError(
100-
f"ORCA failed with return code {return_code}:\n{orca_log_err}"
101-
)
102-
# read the optimized molecule from the output file
103-
xyzfile = Path(temp_path / inputname).resolve().with_suffix(".xyz")
104-
optimized_molecule = molecule.copy()
105-
optimized_molecule.read_xyz_from_file(xyzfile)
99+
# read the optimized molecule from the output file
100+
xyzfile = Path(temp_path / inputname).resolve().with_suffix(".xyz")
101+
optimized_molecule = molecule.copy()
102+
optimized_molecule.read_xyz_from_file(xyzfile)
106103
return optimized_molecule
107104

108105
def singlepoint(self, molecule: Molecule, ncores: int, verbosity: int = 1) -> str:
@@ -221,17 +218,6 @@ def _gen_input(
221218
orca_input += f"* xyzfile {molecule.charge} {molecule.uhf + 1} {xyzfile}\n"
222219
return orca_input
223220

224-
def _should_use_xtb_driver(self) -> bool:
225-
"""
226-
Determine whether the xTB driver should be used for this optimization.
227-
"""
228-
if not self.xtb_driver_enabled or not self.xtb_cfg:
229-
return False
230-
if hasattr(self.xtb_cfg, "has_constraints"):
231-
return self.xtb_cfg.has_constraints()
232-
constraints = getattr(self.xtb_cfg, "distance_constraints", None)
233-
return bool(constraints)
234-
235221
def optimize_xtb_driver(
236222
self,
237223
temp_path: Path,
@@ -260,6 +246,9 @@ def optimize_xtb_driver(
260246
print("ORCA input file:\n##################")
261247
print(orca_input)
262248
print("##################")
249+
print("XTB input file:\n##################")
250+
print(xtb_input)
251+
print("##################")
263252
with open(temp_path / inputname, "w", encoding="utf8") as f:
264253
f.write(orca_input)
265254
# run orca with xTB as a driver
@@ -296,55 +285,24 @@ def _run_xtb_driver(
296285
"""
297286
Run the optimization through the xTB external driver when constraints are requested.
298287
"""
299-
xtb_executable = self._get_xtb_executable()
288+
xtb_executable = get_xtb_path()
289+
if self.xtb_cfg is None:
290+
raise RuntimeError(
291+
"xTB driver requested but no xTB configuration provided."
292+
)
293+
xtb_runner = XTB(path=xtb_executable, xtb_config=self.xtb_cfg)
300294
arguments = [
301-
str(xtb_executable),
302295
geometry_filename,
303296
"--opt",
304297
]
305298
opt_level = getattr(self.cfg, "optlevel", None)
306299
if opt_level not in (None, ""):
307300
arguments.append(str(opt_level))
308301
arguments.extend(["--orca", "-I", xcontrol_name])
309-
try:
310-
xtb_out = sp.run(
311-
arguments,
312-
cwd=temp_path,
313-
capture_output=True,
314-
check=True,
315-
)
316-
xtb_log_out = xtb_out.stdout.decode("utf8", errors="replace")
317-
xtb_log_err = xtb_out.stderr.decode("utf8", errors="replace")
318-
return xtb_log_out, xtb_log_err, 0
319-
except sp.CalledProcessError as e:
320-
xtb_log_out = e.stdout.decode("utf8", errors="replace")
321-
xtb_log_err = e.stderr.decode("utf8", errors="replace")
322-
return xtb_log_out, xtb_log_err, e.returncode
323-
324-
def _get_xtb_executable(self) -> Path:
325-
"""
326-
Determine the path to the xTB executable for external ORCA optimizations.
327-
"""
328-
candidates: list[ORCAConfig | XTBConfig | None] = [self.xtb_cfg, self.cfg]
329-
for source in candidates:
330-
if source is None:
331-
continue
332-
for attr_name in ("xtb_path",):
333-
candidate = getattr(source, attr_name, None)
334-
if not candidate:
335-
continue
336-
try:
337-
return get_xtb_path(candidate)
338-
except ImportError as exc:
339-
raise RuntimeError(
340-
f"xTB executable defined via '{attr_name}' could not be found."
341-
) from exc
342-
try:
343-
return get_xtb_path(None)
344-
except ImportError as exc:
345-
raise RuntimeError(
346-
"xTB executable not found. Required for constrained ORCA optimizations."
347-
) from exc
302+
xtb_log_out, xtb_log_err, returncode = xtb_runner._run(
303+
temp_path=temp_path, arguments=arguments
304+
)
305+
return xtb_log_out, xtb_log_err, returncode
348306

349307
def _write_xtb_input(
350308
self, molecule: Molecule, xtb_input: Path, input_file: str
@@ -356,7 +314,7 @@ def _write_xtb_input(
356314
raise RuntimeError(
357315
"xTB configuration missing but constraints were requested."
358316
)
359-
xtb_path = self._get_xtb_executable()
317+
xtb_path = get_xtb_path()
360318
xtb_writer = XTB(xtb_path, self.xtb_cfg)
361319
generated = xtb_writer._prepare_distance_constraint_file(
362320
molecule, xtb_input.parent

test/test_qm/test_orca.py

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,11 @@ def _factory(cfg=None, xtb_cfg_param=None):
4444

4545

4646
def test_run_xtb_driver_success(monkeypatch, tmp_path, make_orca):
47-
orca = make_orca(cfg=DummyORCAConfig(optlevel="tight"))
48-
monkeypatch.setattr(orca, "_get_xtb_executable", lambda: Path("/fake/xtb"))
47+
orca = make_orca(
48+
cfg=DummyORCAConfig(optlevel="tight"),
49+
xtb_cfg_param=DummyXTBConfig(),
50+
)
51+
monkeypatch.setattr("mindlessgen.qm.orca.get_xtb_path", lambda: Path("/fake/xtb"))
4952
captured = {}
5053

5154
def fake_run(args, cwd, capture_output, check):
@@ -72,8 +75,8 @@ def fake_run(args, cwd, capture_output, check):
7275

7376
def test_run_xtb_driver_failure_returns_error(monkeypatch, tmp_path, make_orca):
7477
"""Ensure the ORCA wrapper surfaces errors from the xTB driver."""
75-
orca = make_orca()
76-
monkeypatch.setattr(orca, "_get_xtb_executable", lambda: Path("/fake/xtb"))
78+
orca = make_orca(xtb_cfg_param=DummyXTBConfig())
79+
monkeypatch.setattr("mindlessgen.qm.orca.get_xtb_path", lambda: Path("/fake/xtb"))
7780

7881
def fake_run(*_, **kwargs):
7982
del kwargs
@@ -86,55 +89,19 @@ def fake_run(*_, **kwargs):
8689
assert (out, err, code) == ("bad", "worse", 1)
8790

8891

89-
def test_get_xtb_executable_raises_when_missing(monkeypatch, make_orca):
92+
def test_run_xtb_driver_requires_xtb_cfg(monkeypatch, tmp_path, make_orca):
9093
orca = make_orca()
91-
92-
def fake_get_xtb_path(candidate):
93-
raise ImportError("not found")
94-
95-
monkeypatch.setattr("mindlessgen.qm.orca.get_xtb_path", fake_get_xtb_path)
96-
with pytest.raises(RuntimeError, match="xTB executable not found"):
97-
orca._get_xtb_executable()
98-
99-
100-
def test_get_xtb_executable_prefers_xtb_cfg_path(monkeypatch, make_orca):
101-
xtb_cfg_constraints = DummyXTBConfig()
102-
xtb_cfg_constraints.xtb_path = "xtb_from_xtb_cfg"
103-
orca = make_orca(xtb_cfg_param=xtb_cfg_constraints)
104-
called = {}
105-
106-
def fake_get_xtb_path(candidate):
107-
called.setdefault("candidates", []).append(candidate)
108-
return Path("/resolved/xtb_cfg")
109-
110-
monkeypatch.setattr("mindlessgen.qm.orca.get_xtb_path", fake_get_xtb_path)
111-
assert orca._get_xtb_executable() == Path("/resolved/xtb_cfg")
112-
assert called["candidates"][0] == "xtb_from_xtb_cfg"
113-
114-
115-
def test_should_use_xtb_driver_checks_distance_constraints(make_orca):
116-
cfg = DummyORCAConfig(use_xtb_driver=True)
117-
xtb_constraints = DummyXTBConfig(distance_constraints=[object()])
118-
orca = make_orca(cfg=cfg, xtb_cfg_param=xtb_constraints)
119-
assert orca._should_use_xtb_driver() is True
120-
121-
no_constraints = DummyXTBConfig(distance_constraints=[])
122-
orca_no_constraints = make_orca(cfg=cfg, xtb_cfg_param=no_constraints)
123-
assert orca_no_constraints._should_use_xtb_driver() is False
124-
125-
cfg_disabled = DummyORCAConfig(use_xtb_driver=False)
126-
orca_disabled = make_orca(cfg=cfg_disabled, xtb_cfg_param=xtb_constraints)
127-
assert orca_disabled._should_use_xtb_driver() is False
128-
orca_missing_xtb = make_orca(cfg=cfg, xtb_cfg_param=None)
129-
assert orca_missing_xtb._should_use_xtb_driver() is False
94+
monkeypatch.setattr("mindlessgen.qm.orca.get_xtb_path", lambda: Path("/fake/xtb"))
95+
with pytest.raises(RuntimeError, match="xTB driver requested"):
96+
orca._run_xtb_driver(tmp_path, "geom.xyz", "ctrl.inp", ncores=1)
13097

13198

13299
def test_write_xtb_input_creates_expected_file(monkeypatch, tmp_path, make_orca):
133100
xtb_cfg_instance = DummyXTBConfig(
134101
distance_constraints=["dummy"], distance_constraint_force_constant=0.7
135102
)
136103
orca = make_orca(xtb_cfg_param=xtb_cfg_instance)
137-
monkeypatch.setattr(orca, "_get_xtb_executable", lambda: Path("/fake/xtb"))
104+
monkeypatch.setattr("mindlessgen.qm.orca.get_xtb_path", lambda: Path("/fake/xtb"))
138105

139106
def fake_prepare(self, molecule, temp_dir):
140107
assert temp_dir == tmp_path

0 commit comments

Comments
 (0)