Skip to content

Commit 465e6ac

Browse files
If constrains are set in the refinement step now the same constrains are set in the postprocess via the usage of xtb with orca as a driver. (#145)
* If constrains are set during refinement the postprocess step now uses xtb with orca as a driver to set the same constrains during postprocess. Signed-off-by: Jonathan Schöps <s6jtscho@uni-bonn.de> * Implemented tests for the orca as a driver run when distance constrains are enabled Signed-off-by: Jonathan Schöps <s6jtscho@uni-bonn.de> * Fix for the windows python crash in testsuit Signed-off-by: Jonathan Schöps <s6jtscho@uni-bonn.de> * Try to fix for the windows python crash in testsuit Signed-off-by: Jonathan Schöps <s6jtscho@uni-bonn.de> * Removal of copied code Signed-off-by: Jonathan Schöps <s6jtscho@uni-bonn.de> * Implementation of comments from the Pull request, less complicated optimize in orca.py Signed-off-by: Jonathan Schöps <s6jtscho@uni-bonn.de> * Implemented requested changes and updated tests Signed-off-by: Jonathan Schöps <s6jtscho@uni-bonn.de> * changed the test_orca.py testsuite to match the test_xtb.py tests Signed-off-by: Jonathan Schöps <s6jtscho@uni-bonn.de> --------- Signed-off-by: Jonathan Schöps <s6jtscho@uni-bonn.de>
1 parent e232c28 commit 465e6ac

6 files changed

Lines changed: 431 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Distance constraints for xTB (normalized parsing, config validation, runtime enforcement, and optional integration test).
13+
- Distance constrains for ORCA (uses xTB as as driver to set the same distance constrains for the postprocess).
1314

1415
## [0.6.0] - 2025-04-01
1516

mindlessgen.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ basis = "def2-SVP"
106106
gridsize = 1
107107
# > Maximum number of SCF cycles: Options: <int>
108108
scf_cycles = 100
109+
# > Use xTB as an external ORCA driver to keep distance constraints during postprocessing. Options: <bool>
110+
use_xtb_driver = false
109111

110112
[turbomole]
111113
# > Path to the ridft executable. The name `ridft` is automatically searched for. Options: <str | Path>

src/mindlessgen/generator/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ def setup_engines(
460460
raise ImportError("orca not found.")
461461
except ImportError as e:
462462
raise ImportError("orca not found.") from e
463-
return ORCA(path, cfg.orca)
463+
return ORCA(path, cfg.orca, cfg.xtb)
464464
elif engine_type == "turbomole":
465465
try:
466466
jobex_path = jobex_path_func(cfg.turbomole.jobex_path)

src/mindlessgen/prog/config.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1135,6 +1135,7 @@ def __init__(self: ORCAConfig) -> None:
11351135
self._basis: str = "def2-SVP"
11361136
self._gridsize: int = 1
11371137
self._scf_cycles: int = 100
1138+
self._use_xtb_driver: bool = False
11381139

11391140
def get_identifier(self) -> str:
11401141
return "orca"
@@ -1225,6 +1226,22 @@ def scf_cycles(self, max_scf_cycles: int):
12251226
raise ValueError("Max SCF cycles should be greater than 0.")
12261227
self._scf_cycles = max_scf_cycles
12271228

1229+
@property
1230+
def use_xtb_driver(self) -> bool:
1231+
"""
1232+
Determine whether the xTB external driver can be used during post-processing.
1233+
"""
1234+
return self._use_xtb_driver
1235+
1236+
@use_xtb_driver.setter
1237+
def use_xtb_driver(self, enabled: bool):
1238+
"""
1239+
Enable or disable the usage of the xTB external driver during post-processing.
1240+
"""
1241+
if not isinstance(enabled, bool):
1242+
raise TypeError("use_xtb_driver should be a boolean.")
1243+
self._use_xtb_driver = enabled
1244+
12281245

12291246
class TURBOMOLEConfig(BaseConfig):
12301247
"""

src/mindlessgen/qm/orca.py

Lines changed: 166 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@
88
from tempfile import TemporaryDirectory
99

1010
from ..molecules import Molecule
11-
from ..prog import ORCAConfig
11+
from ..prog import ORCAConfig, XTBConfig
1212
from .base import QMMethod
13+
from .xtb import XTB, get_xtb_path
1314

1415

1516
class ORCA(QMMethod):
1617
"""
1718
This class handles all interaction with the ORCA external dependency.
1819
"""
1920

20-
def __init__(self, path: str | Path, orcacfg: ORCAConfig) -> None:
21+
def __init__(
22+
self, path: str | Path, orcacfg: ORCAConfig, xtb_config: XTBConfig | None = None
23+
) -> None:
2124
"""
2225
Initialize the ORCA class.
2326
"""
@@ -28,6 +31,7 @@ def __init__(self, path: str | Path, orcacfg: ORCAConfig) -> None:
2831
else:
2932
raise TypeError("orca_path should be a string or a Path object.")
3033
self.cfg = orcacfg
34+
self.xtb_cfg = xtb_config
3135
# must be explicitly initialized in current parallelization implementation
3236
# as accessing parent class variables might not be possible
3337
self.tmp_dir = self.__class__.get_temporary_directory()
@@ -51,24 +55,38 @@ def optimize(
5155
# NOTE: "prefix" and "dir" are valid keyword arguments for TemporaryDirectory
5256
temp_path = Path(temp_dir).resolve()
5357
# write the molecule to a temporary file
54-
molecule.write_xyz_to_file(temp_path / "molecule.xyz")
58+
xyz_filename = "molecule.xyz"
59+
molecule.write_xyz_to_file(temp_path / xyz_filename)
5560

61+
if self.cfg.use_xtb_driver:
62+
optimized_molecule = self.optimize_xtb_driver(
63+
temp_path=temp_path,
64+
molecule=molecule,
65+
xyz_filename=xyz_filename,
66+
ncores=ncores,
67+
max_cycles=max_cycles,
68+
verbosity=verbosity,
69+
)
70+
return optimized_molecule
5671
inputname = "orca_opt.inp"
5772
orca_input = self._gen_input(
58-
molecule, "molecule.xyz", ncores, True, max_cycles
73+
molecule,
74+
xyz_filename,
75+
temp_path,
76+
ncores,
77+
True,
78+
max_cycles,
5979
)
6080
if verbosity > 1:
6181
print("ORCA input file:\n##################")
6282
print(orca_input)
6383
print("##################")
6484
with open(temp_path / inputname, "w", encoding="utf8") as f:
6585
f.write(orca_input)
66-
6786
# run orca
6887
arguments = [
6988
inputname,
7089
]
71-
7290
orca_log_out, orca_log_err, return_code = self._run(
7391
temp_path=temp_path, arguments=arguments
7492
)
@@ -78,7 +96,6 @@ def optimize(
7896
raise RuntimeError(
7997
f"ORCA failed with return code {return_code}:\n{orca_log_err}"
8098
)
81-
8299
# read the optimized molecule from the output file
83100
xyzfile = Path(temp_path / inputname).resolve().with_suffix(".xyz")
84101
optimized_molecule = molecule.copy()
@@ -103,10 +120,10 @@ def singlepoint(self, molecule: Molecule, ncores: int, verbosity: int = 1) -> st
103120

104121
# write the input file
105122
inputname = "orca.inp"
106-
orca_input = self._gen_input(molecule, molfile, ncores)
123+
orca_input = self._gen_input(molecule, molfile, temp_path, ncores)
107124
if verbosity > 1:
108125
print("ORCA input file:\n##################")
109-
print(self._gen_input(molecule, molfile, ncores))
126+
print(self._gen_input(molecule, molfile, temp_path, ncores))
110127
print("##################")
111128
with open(temp_path / inputname, "w", encoding="utf8") as f:
112129
f.write(orca_input)
@@ -174,6 +191,7 @@ def _gen_input(
174191
self,
175192
molecule: Molecule,
176193
xyzfile: str,
194+
_temp_path: Path,
177195
ncores: int,
178196
optimization: bool = False,
179197
opt_cycles: int | None = None,
@@ -200,6 +218,145 @@ def _gen_input(
200218
orca_input += f"* xyzfile {molecule.charge} {molecule.uhf + 1} {xyzfile}\n"
201219
return orca_input
202220

221+
def optimize_xtb_driver(
222+
self,
223+
temp_path: Path,
224+
molecule: Molecule,
225+
xyz_filename: str,
226+
ncores: int,
227+
max_cycles: int | None = None,
228+
verbosity: int = 1,
229+
) -> Molecule:
230+
"""
231+
Optimize a molecule using ORCA through the xTB external driver.
232+
"""
233+
234+
xtb_input = temp_path / "xtb.inp"
235+
inputname = "orca_opt.inp"
236+
self._write_xtb_input(molecule, xtb_input, inputname)
237+
orca_input = self._gen_input_xtb_driver(
238+
molecule,
239+
xyz_filename,
240+
temp_path,
241+
ncores,
242+
True,
243+
max_cycles,
244+
)
245+
if verbosity > 1:
246+
print("ORCA input file:\n##################")
247+
print(orca_input)
248+
print("##################")
249+
print("XTB input file:\n##################")
250+
print(xtb_input)
251+
print("##################")
252+
with open(temp_path / inputname, "w", encoding="utf8") as f:
253+
f.write(orca_input)
254+
# run orca with xTB as a driver
255+
orca_log_out, orca_log_err, return_code = self._run_xtb_driver(
256+
temp_path=temp_path,
257+
geometry_filename=xyz_filename,
258+
xcontrol_name=xtb_input.name,
259+
ncores=ncores,
260+
)
261+
if verbosity > 2:
262+
print(orca_log_out)
263+
if return_code != 0:
264+
raise RuntimeError(
265+
f"ORCA failed with return code {return_code}:\n{orca_log_err}"
266+
)
267+
268+
# read the optimized molecule from the output file
269+
xyzfile = temp_path / "xtbopt.xyz"
270+
if not xyzfile.exists():
271+
raise RuntimeError(
272+
"xTB-driven ORCA optimization did not produce 'xtbopt.xyz'."
273+
)
274+
optimized_molecule = molecule.copy()
275+
optimized_molecule.read_xyz_from_file(xyzfile)
276+
return optimized_molecule
277+
278+
def _run_xtb_driver(
279+
self,
280+
temp_path: Path,
281+
geometry_filename: str,
282+
xcontrol_name: str,
283+
ncores: int,
284+
) -> tuple[str, str, int]:
285+
"""
286+
Run the optimization through the xTB external driver when constraints are requested.
287+
"""
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)
294+
arguments = [
295+
geometry_filename,
296+
"--opt",
297+
]
298+
opt_level = getattr(self.cfg, "optlevel", None)
299+
if opt_level not in (None, ""):
300+
arguments.append(str(opt_level))
301+
arguments.extend(["--orca", "-I", xcontrol_name])
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
306+
307+
def _write_xtb_input(
308+
self, molecule: Molecule, xtb_input: Path, input_file: str
309+
) -> None:
310+
"""
311+
Write the xcontrol file containing constraints and ORCA driver info.
312+
"""
313+
if not self.xtb_cfg:
314+
raise RuntimeError(
315+
"xTB configuration missing but constraints were requested."
316+
)
317+
xtb_path = get_xtb_path()
318+
xtb_writer = XTB(xtb_path, self.xtb_cfg)
319+
generated = xtb_writer._prepare_distance_constraint_file(
320+
molecule, xtb_input.parent
321+
)
322+
if not generated:
323+
raise RuntimeError(
324+
"xTB driver requested but no distance constraints were generated."
325+
)
326+
with xtb_input.open("a", encoding="utf8") as handle:
327+
handle.write("$external\n")
328+
handle.write(f" orca input file= {input_file}\n")
329+
handle.write(f" orca bin= {self.path}\n")
330+
handle.write("$end\n")
331+
332+
def _gen_input_xtb_driver(
333+
self,
334+
molecule: Molecule,
335+
xyzfile: str,
336+
temp_path: Path,
337+
ncores: int,
338+
optimization: bool = False,
339+
opt_cycles: int | None = None,
340+
) -> str:
341+
"""
342+
Generate a default input file for ORCA.
343+
"""
344+
orca_input = f"! {self.cfg.functional} {self.cfg.basis}\n"
345+
orca_input += f"! DEFGRID{self.cfg.gridsize}\n"
346+
orca_input += "! MiniPrint\n"
347+
orca_input += "! NoTRAH\n"
348+
orca_input += "! Engrad\n"
349+
# "! AutoAux" keyword for super-heavy elements as def2/J ends at Rn
350+
if any(atom >= 86 for atom in molecule.ati):
351+
orca_input += "! AutoAux\n"
352+
orca_input += f"%scf\n\tMaxIter {self.cfg.scf_cycles}\n"
353+
if not optimization:
354+
orca_input += "\tConvergence Medium\n"
355+
orca_input += "end\n"
356+
orca_input += f"%pal nprocs {ncores} end\n\n"
357+
orca_input += f"* xyzfile {molecule.charge} {molecule.uhf + 1} {xyzfile}\n"
358+
return orca_input
359+
203360

204361
# TODO: 1. Convert this to a @staticmethod of Class ORCA
205362
# 2. Rename to `get_method` or similar to enable an abstract interface

0 commit comments

Comments
 (0)