qccompute 0.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qccompute/__init__.py +10 -0
- qccompute/adapters/__init__.py +12 -0
- qccompute/adapters/base.py +325 -0
- qccompute/adapters/crest.py +134 -0
- qccompute/adapters/file.py +49 -0
- qccompute/adapters/geometric.py +337 -0
- qccompute/adapters/orca.py +102 -0
- qccompute/adapters/qcengine.py +68 -0
- qccompute/adapters/terachem.py +173 -0
- qccompute/adapters/terachem_fe.py +78 -0
- qccompute/adapters/terachem_pbs.py +13 -0
- qccompute/adapters/utils.py +302 -0
- qccompute/adapters/xtb.py +156 -0
- qccompute/exceptions.py +153 -0
- qccompute/main.py +136 -0
- qccompute/py.typed +0 -0
- qccompute/utils.py +112 -0
- qccompute-0.13.0.dist-info/METADATA +222 -0
- qccompute-0.13.0.dist-info/RECORD +21 -0
- qccompute-0.13.0.dist-info/WHEEL +4 -0
- qccompute-0.13.0.dist-info/licenses/LICENSE +21 -0
qccompute/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from importlib import metadata as _metadata
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
__version__ = _metadata.version(__name__)
|
|
5
|
+
except _metadata.PackageNotFoundError:
|
|
6
|
+
# Source tree / build hook / CI checkout
|
|
7
|
+
__version__ = "0.0.0+local"
|
|
8
|
+
|
|
9
|
+
from .main import compute, compute_args # noqa: F401
|
|
10
|
+
from .utils import get_adapter # noqa: F401
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Must import all adapters here for them to be found by the AdapterRegistry."""
|
|
2
|
+
|
|
3
|
+
from .base import * # noqa: F403
|
|
4
|
+
from .crest import CRESTAdapter # noqa: F401
|
|
5
|
+
from .file import FileAdapter # noqa: F401
|
|
6
|
+
from .geometric import GeometricAdapter # noqa: F401
|
|
7
|
+
from .orca import OrcaAdapter # noqa: F401
|
|
8
|
+
from .qcengine import QCEngineAdapter # noqa: F401
|
|
9
|
+
from .terachem import TeraChemAdapter # noqa: F401
|
|
10
|
+
from .terachem_fe import TeraChemFEAdapter # noqa: F401
|
|
11
|
+
from .terachem_pbs import TeraChemPBSAdapter # noqa: F401
|
|
12
|
+
from .xtb import XTBAdapter # noqa: F401
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from time import time
|
|
5
|
+
from typing import Any, Generic
|
|
6
|
+
|
|
7
|
+
from qcdata import (
|
|
8
|
+
CalcType,
|
|
9
|
+
Data,
|
|
10
|
+
DataType,
|
|
11
|
+
FileInput,
|
|
12
|
+
Files,
|
|
13
|
+
InputType,
|
|
14
|
+
ProgramOutput,
|
|
15
|
+
StructuredInputs,
|
|
16
|
+
)
|
|
17
|
+
from qcdata.helper_types import StrOrPath
|
|
18
|
+
|
|
19
|
+
from qccompute.exceptions import (
|
|
20
|
+
AdapterInputError,
|
|
21
|
+
ProgramNotFoundError,
|
|
22
|
+
QCComputeBaseError,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
from .utils import construct_provenance, tmpdir
|
|
26
|
+
|
|
27
|
+
__all__ = ["BaseAdapter", "registry"]
|
|
28
|
+
|
|
29
|
+
# Registry for all Adaptors.
|
|
30
|
+
# NOTE: Registry stores class objects, not instances.
|
|
31
|
+
# Use registry[program]() to instantiate an adaptor
|
|
32
|
+
# Or use the higher level qccompute.utils.get_adapter() function.
|
|
33
|
+
registry = {}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BaseAdapter(ABC, Generic[InputType, DataType]):
|
|
37
|
+
"""Base class for all adapters."""
|
|
38
|
+
|
|
39
|
+
# Whether to this program reads or writes files to disk.
|
|
40
|
+
# If True, the adapter will write all files from input_data to a disk before executing
|
|
41
|
+
# the program. If False, the adapter must handle input files itself in some other
|
|
42
|
+
# way. Generally this should be True for most adapters unless the program can
|
|
43
|
+
# handle input files directly from memory, stdin, or some other mechanism that is
|
|
44
|
+
# more efficient than writing to disk.
|
|
45
|
+
uses_files = True
|
|
46
|
+
program: str # All subclasses must define this attribute.
|
|
47
|
+
|
|
48
|
+
def program_version(self, stdout: str | None) -> str | None:
|
|
49
|
+
"""Return program version. Adapters should override this method.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
stdout: The stdout from the program. Because running "program --version"
|
|
53
|
+
can be extremely slow for some programs, the stdout from the program
|
|
54
|
+
is passed in here so that the version can be extracted from it if
|
|
55
|
+
possible. If the version cannot be extracted from the stdout, then
|
|
56
|
+
this function should return the program version in some other way.
|
|
57
|
+
"""
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def validate_input(self, input_data: InputType) -> None:
|
|
62
|
+
"""Validate input_data to ensure compatibility with adapter.
|
|
63
|
+
Adapters should override this method.
|
|
64
|
+
"""
|
|
65
|
+
raise NotImplementedError
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
def compute_data(
|
|
69
|
+
self,
|
|
70
|
+
input_data: InputType,
|
|
71
|
+
update_func: Callable | None = None,
|
|
72
|
+
update_interval: float | None = None,
|
|
73
|
+
**kwargs,
|
|
74
|
+
) -> tuple[DataType, str]:
|
|
75
|
+
"""Subclasses should implement this method with custom compute logic."""
|
|
76
|
+
raise NotImplementedError
|
|
77
|
+
|
|
78
|
+
def compute(
|
|
79
|
+
self,
|
|
80
|
+
input_data: InputType,
|
|
81
|
+
*,
|
|
82
|
+
scratch_dir: StrOrPath | None = None,
|
|
83
|
+
rm_scratch_dir: bool = True,
|
|
84
|
+
collect_logs: bool = True,
|
|
85
|
+
collect_files: bool = False,
|
|
86
|
+
collect_wfn: bool = False,
|
|
87
|
+
update_func: Callable | None = None,
|
|
88
|
+
update_interval: float | None = None,
|
|
89
|
+
print_logs: bool = False,
|
|
90
|
+
raise_exc: bool = True,
|
|
91
|
+
propagate_wfn: bool = False,
|
|
92
|
+
**adapter_kwargs,
|
|
93
|
+
) -> ProgramOutput[InputType, DataType]:
|
|
94
|
+
"""Compute the given input using the adapter's program.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
input_data: A qcdata input object for a computation. E.g. A FileInput,
|
|
98
|
+
ProgramInput or DualProgramInput.
|
|
99
|
+
scratch_dir: The scratch directory for the program. If None, a new directory
|
|
100
|
+
is created in the system default temporary directory. If rm_scratch_dir
|
|
101
|
+
is True this directory will be deleted after the program finishes.
|
|
102
|
+
rm_scratch_dir: Delete the scratch directory after the program exits.
|
|
103
|
+
collect_logs: Whether to collect stdout/stderr from the program as output.
|
|
104
|
+
Failed computations will always collect stdout/stderr.
|
|
105
|
+
collect_files: Collect all files generated by the QC program as output.
|
|
106
|
+
collect_wfn: Collect the wavefunction file(s) from the calculation.
|
|
107
|
+
Not every program will support this. Use collect_files to collect
|
|
108
|
+
all files including the wavefunction.
|
|
109
|
+
update_func: A function to call as the program executes. The function must
|
|
110
|
+
accept the in-process stdout/stderr output as a string for its first
|
|
111
|
+
argument.
|
|
112
|
+
update_interval: The minimum time in seconds between calls to the
|
|
113
|
+
update_func.
|
|
114
|
+
print_logs: Whether to print stdout/stderr to the terminal in real time as
|
|
115
|
+
the program executes. Will be ignored if an update_func passed.
|
|
116
|
+
raise_exc: If False, qccompute will return a ProgramOutput object when the QC
|
|
117
|
+
program fails rather than raise an exception.
|
|
118
|
+
propagate_wfn: For any adapter performing a sequential task, such
|
|
119
|
+
as a geometry optimization, propagate the wavefunction from the previous
|
|
120
|
+
step to the next step. This is useful for accelerating convergence by
|
|
121
|
+
using a previously computed wavefunction as a starting guess. If an
|
|
122
|
+
adapter does not support wavefunction propagation, an AdapterInputError
|
|
123
|
+
will be raised.
|
|
124
|
+
**adapter_kwargs: Additional keyword arguments to pass to the adapter or
|
|
125
|
+
qcng.compute().
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
A ProgramOutput object containing the results of the computation.
|
|
129
|
+
|
|
130
|
+
Raises:
|
|
131
|
+
AdapterNotFoundError: If the program is not supported (i.e., no Adapter
|
|
132
|
+
is implemented for the program in qccompute or qcengine).
|
|
133
|
+
ProgramNotFoundError: If the program executable is not found on the
|
|
134
|
+
system at execution time. This likely means the program is not installed
|
|
135
|
+
or not available on the $PATH.
|
|
136
|
+
AdapterInputError: If the input is invalid for the adapter.
|
|
137
|
+
ExternalProgramExecutionError: If the QC program fails during execution.
|
|
138
|
+
QCEngineError: If QCEngine performs the computation raises an error.
|
|
139
|
+
"""
|
|
140
|
+
# Print stdout to terminal in real time as program executes
|
|
141
|
+
if print_logs and update_func is None:
|
|
142
|
+
update_func, update_interval = (
|
|
143
|
+
lambda _, stdout_new: print(stdout_new),
|
|
144
|
+
0.1,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# cd to a temporary directory to run the program.
|
|
148
|
+
with tmpdir(self.uses_files, scratch_dir, rm_scratch_dir) as final_scratch_dir:
|
|
149
|
+
if self.uses_files: # Write non structured input files to disk.
|
|
150
|
+
input_data.save_files()
|
|
151
|
+
|
|
152
|
+
# Define outputs
|
|
153
|
+
prog_output_dict: dict[str, Any] = {}
|
|
154
|
+
logs: str | None = None
|
|
155
|
+
data: Data
|
|
156
|
+
exc: QCComputeBaseError | None = None
|
|
157
|
+
program_version: str | None = None
|
|
158
|
+
|
|
159
|
+
start = time()
|
|
160
|
+
try:
|
|
161
|
+
# Validate input object
|
|
162
|
+
self.validate_input(input_data)
|
|
163
|
+
|
|
164
|
+
# Execute the program. data will be Files for FileInput
|
|
165
|
+
data, logs = self.compute_data(
|
|
166
|
+
input_data,
|
|
167
|
+
update_func,
|
|
168
|
+
update_interval,
|
|
169
|
+
propagate_wfn=propagate_wfn,
|
|
170
|
+
**adapter_kwargs,
|
|
171
|
+
)
|
|
172
|
+
# None value covers FileInput case
|
|
173
|
+
prog_output_dict["success"] = True
|
|
174
|
+
|
|
175
|
+
# Optionally collect wavefunction file
|
|
176
|
+
if collect_wfn and not collect_files:
|
|
177
|
+
data.files.update(self.collect_wfn())
|
|
178
|
+
|
|
179
|
+
except QCComputeBaseError as e:
|
|
180
|
+
exc = e
|
|
181
|
+
prog_output_dict["success"] = False
|
|
182
|
+
# Any half-completed data
|
|
183
|
+
data = getattr(e, "data") or Files()
|
|
184
|
+
logs = getattr(e, "logs", logs) or logs
|
|
185
|
+
# For mypy because e.logs is not of a known type
|
|
186
|
+
logs = str(logs) if logs is not None else None
|
|
187
|
+
prog_output_dict["traceback"] = traceback.format_exc()
|
|
188
|
+
|
|
189
|
+
wall_time = time() - start
|
|
190
|
+
|
|
191
|
+
# Check for parsed version in extras at default location
|
|
192
|
+
program_version = data.extras.get("program_version")
|
|
193
|
+
if not program_version:
|
|
194
|
+
try:
|
|
195
|
+
program_version = self.program_version(logs)
|
|
196
|
+
except ProgramNotFoundError:
|
|
197
|
+
pass # program_version = None set above
|
|
198
|
+
|
|
199
|
+
# Construct Provenance object
|
|
200
|
+
provenance = construct_provenance(
|
|
201
|
+
self.program,
|
|
202
|
+
program_version,
|
|
203
|
+
final_scratch_dir,
|
|
204
|
+
wall_time,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Always collect for failures; otherwise obey collect_logs
|
|
208
|
+
logs = logs if not prog_output_dict["success"] or collect_logs else None
|
|
209
|
+
|
|
210
|
+
# Construct ProgramOutput
|
|
211
|
+
prog_output_dict.update(
|
|
212
|
+
{
|
|
213
|
+
"input_data": input_data,
|
|
214
|
+
"logs": logs,
|
|
215
|
+
"data": data,
|
|
216
|
+
"provenance": provenance,
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
prog_output = ProgramOutput[InputType, DataType](**prog_output_dict)
|
|
220
|
+
|
|
221
|
+
# Collect files generated by the program
|
|
222
|
+
if self.uses_files and (collect_files or type(input_data) is FileInput):
|
|
223
|
+
prog_output.data.add_files(
|
|
224
|
+
final_scratch_dir,
|
|
225
|
+
recursive=True,
|
|
226
|
+
exclude=list(input_data.files.keys()),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Append ProgramOutput to exception and raise if raise_exc=True
|
|
230
|
+
# Helpful for BigChem and ChemCloud exception handling
|
|
231
|
+
if raise_exc and exc:
|
|
232
|
+
exc.prog_output = prog_output
|
|
233
|
+
raise exc
|
|
234
|
+
|
|
235
|
+
return prog_output
|
|
236
|
+
|
|
237
|
+
def collect_wfn(self) -> dict[str, str | bytes]:
|
|
238
|
+
"""Collect the wavefunction file(s) from the scratch_dir.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Dictionary of filenames and file data. E.g. {"c0": b"filedata"}
|
|
242
|
+
|
|
243
|
+
"""
|
|
244
|
+
# Collect wavefunction file from the calc_dir
|
|
245
|
+
raise AdapterInputError(
|
|
246
|
+
program=self.program,
|
|
247
|
+
message=f"Adapter for {self.program} does not support wavefunction collection.",
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class ProgramAdapter(BaseAdapter, Generic[InputType, DataType]):
|
|
252
|
+
"""Base adapter for all program adapters (all but FileAdaptor)."""
|
|
253
|
+
|
|
254
|
+
supported_calctypes: list[
|
|
255
|
+
CalcType
|
|
256
|
+
] # All subclasses must specify supported calctypes
|
|
257
|
+
|
|
258
|
+
def __init_subclass__(cls, **kwargs):
|
|
259
|
+
super().__init_subclass__(**kwargs)
|
|
260
|
+
# Ensure that subclasses define the required class attributes
|
|
261
|
+
if not getattr(cls, "program", None):
|
|
262
|
+
raise NotImplementedError(
|
|
263
|
+
f"Subclasses of {ProgramAdapter.__name__} must define a program "
|
|
264
|
+
f"string. {cls.__name__} does not meet this requirement."
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Automatically register all subclasses
|
|
268
|
+
registry[cls.program] = cls
|
|
269
|
+
|
|
270
|
+
if not getattr(cls, "supported_calctypes", None):
|
|
271
|
+
raise NotImplementedError(
|
|
272
|
+
f"Subclasses of {ProgramAdapter.__name__} must define a nonempty "
|
|
273
|
+
f"supported_calctypes list. {cls.__name__} does not meet this "
|
|
274
|
+
"requirement."
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
@abstractmethod
|
|
278
|
+
def program_version(self, stdout: str | None) -> str:
|
|
279
|
+
"""Get the version of the program.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
stdout: The stdout from the program. Because running "program --version"
|
|
283
|
+
can be extremely slow for some programs, the stdout from the program
|
|
284
|
+
is passed in here so that the version can be extracted from it if
|
|
285
|
+
possible. If the version cannot be extracted from the stdout, then
|
|
286
|
+
this function should return the program version in some other way.
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
@abstractmethod
|
|
290
|
+
def compute_data(
|
|
291
|
+
self,
|
|
292
|
+
input_data: InputType,
|
|
293
|
+
update_func: Callable | None = None,
|
|
294
|
+
update_interval: float | None = None,
|
|
295
|
+
**kwargs,
|
|
296
|
+
) -> tuple[DataType, str]:
|
|
297
|
+
"""All ProgramAdapters must return a DataType."""
|
|
298
|
+
raise NotImplementedError
|
|
299
|
+
|
|
300
|
+
def validate_input(self, input_data: StructuredInputs) -> None:
|
|
301
|
+
"""Validate the input object for compatibility with the adapter.
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
input_data: The input object to validate.
|
|
305
|
+
|
|
306
|
+
Raises:
|
|
307
|
+
AdapterInputError: If the input object's calctype is not supported.
|
|
308
|
+
"""
|
|
309
|
+
if input_data.calctype not in self.supported_calctypes:
|
|
310
|
+
raise AdapterInputError(
|
|
311
|
+
program=self.program,
|
|
312
|
+
message=(
|
|
313
|
+
f"The {self.program} adapter does not yet support "
|
|
314
|
+
f"'{input_data.calctype.value}' calculations. This adaptor can "
|
|
315
|
+
f"compute: {[ct.value for ct in self.supported_calctypes]}"
|
|
316
|
+
),
|
|
317
|
+
)
|
|
318
|
+
if input_data.files and not self.uses_files:
|
|
319
|
+
raise AdapterInputError(
|
|
320
|
+
program=self.program,
|
|
321
|
+
message=(
|
|
322
|
+
f"The {self.program} adapter does not support files as additional "
|
|
323
|
+
"inputs. Remove the files from your input."
|
|
324
|
+
),
|
|
325
|
+
)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Adapter for CREST package. https://crest-lab.github.io/crest-docs/"""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import qccodec
|
|
7
|
+
from qccodec.parsers.crest import parse_version
|
|
8
|
+
from qcdata import (
|
|
9
|
+
CalcType,
|
|
10
|
+
ConformerSearchResults,
|
|
11
|
+
OptimizationResults,
|
|
12
|
+
ProgramInput,
|
|
13
|
+
SinglePointData,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from qccompute.exceptions import AdapterInputError, ExternalProgramError
|
|
17
|
+
|
|
18
|
+
from .base import ProgramAdapter
|
|
19
|
+
from .utils import execute_subprocess
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CRESTAdapter(
|
|
23
|
+
ProgramAdapter[
|
|
24
|
+
ProgramInput,
|
|
25
|
+
SinglePointData | OptimizationResults | ConformerSearchResults,
|
|
26
|
+
]
|
|
27
|
+
):
|
|
28
|
+
"""Adapter for CREST.
|
|
29
|
+
|
|
30
|
+
Note:
|
|
31
|
+
The `ProgramInput.keywords` attribute is used to create the input file for
|
|
32
|
+
CREST. This means that the structure of the `keywords` attribute should match
|
|
33
|
+
that of [CREST's input specification](https://crest-lab.github.io/crest-docs/page/documentation/inputfiles.html).
|
|
34
|
+
Keywords such as method, charge, and uhf (which are stored on the `Model` and
|
|
35
|
+
`Structure`; uhf is `multiplicity - 1`) will be added to the input file
|
|
36
|
+
automatically.
|
|
37
|
+
|
|
38
|
+
Warning:
|
|
39
|
+
CREST does not exit with a non-zero exit code on failure. Instead, it prints
|
|
40
|
+
"FAILED" in the stdout. This adapter will raise an ExternalProgramError if
|
|
41
|
+
"FAILED" is found in the stdout.
|
|
42
|
+
|
|
43
|
+
Warning:
|
|
44
|
+
CREST automatically translates the input geometry to the origin. This means
|
|
45
|
+
that the input geometry printed to CREST's stdout will not match the input
|
|
46
|
+
structure; however, all computed values (such as energies, gradients, etc.) are
|
|
47
|
+
still valid because they are translationally invariant.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
supported_calctypes = [
|
|
51
|
+
CalcType.energy,
|
|
52
|
+
CalcType.gradient,
|
|
53
|
+
CalcType.hessian,
|
|
54
|
+
CalcType.optimization,
|
|
55
|
+
CalcType.conformer_search,
|
|
56
|
+
]
|
|
57
|
+
"""Supported calculation types."""
|
|
58
|
+
program = "crest"
|
|
59
|
+
|
|
60
|
+
def program_version(self, stdout: str | None = None) -> str:
|
|
61
|
+
"""Get the program version.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
stdout: The stdout from the program.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The program version.
|
|
68
|
+
"""
|
|
69
|
+
if not stdout:
|
|
70
|
+
stdout = execute_subprocess(self.program, ["--version"])
|
|
71
|
+
return parse_version(stdout)
|
|
72
|
+
|
|
73
|
+
def compute_data(
|
|
74
|
+
self,
|
|
75
|
+
input_data: ProgramInput,
|
|
76
|
+
update_func: Callable | None = None,
|
|
77
|
+
update_interval: float | None = None,
|
|
78
|
+
collect_rotamers: bool = False,
|
|
79
|
+
**kwargs,
|
|
80
|
+
) -> tuple[SinglePointData | OptimizationResults | ConformerSearchResults, str]:
|
|
81
|
+
"""Execute CREST on the given input.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
input_data: The qcdata ProgramInput object for a computation.
|
|
85
|
+
update_func: A function to call with the stdout at regular intervals.
|
|
86
|
+
update_interval: The interval at which to call the update function.
|
|
87
|
+
collect_rotamers: Collect rotamers if doing a conformer_search. Defaults to
|
|
88
|
+
False since rotamers are usually not of interest and there will be many.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
A tuple of ConformerSearchResults and the stdout str.
|
|
92
|
+
"""
|
|
93
|
+
# Create CREST native input files
|
|
94
|
+
try:
|
|
95
|
+
native_inp = qccodec.encode(input_data, self.program)
|
|
96
|
+
except qccodec.exceptions.EncoderError as e:
|
|
97
|
+
raise AdapterInputError(program=self.program) from e
|
|
98
|
+
|
|
99
|
+
# Write the input files to disk
|
|
100
|
+
inp_file, struct_file = Path("input.toml"), Path(native_inp.geometry_filename)
|
|
101
|
+
inp_file.write_text(native_inp.input_file)
|
|
102
|
+
struct_file.write_text(native_inp.geometry_file)
|
|
103
|
+
|
|
104
|
+
# Execute CREST
|
|
105
|
+
stdout = execute_subprocess(
|
|
106
|
+
self.program, [inp_file.name], update_func, update_interval
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# CREST does not exit with a non-zero exit code on failure
|
|
110
|
+
if "FAILED" in stdout:
|
|
111
|
+
raise ExternalProgramError(
|
|
112
|
+
program=self.program,
|
|
113
|
+
message=f"CREST calculation failed. See the stdout for more information.",
|
|
114
|
+
logs=stdout,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Parse the output
|
|
118
|
+
try:
|
|
119
|
+
results = qccodec.decode(
|
|
120
|
+
self.program,
|
|
121
|
+
input_data.calctype,
|
|
122
|
+
stdout=stdout,
|
|
123
|
+
directory=".",
|
|
124
|
+
input_data=input_data,
|
|
125
|
+
)
|
|
126
|
+
except qccodec.exceptions.ParserError as e:
|
|
127
|
+
raise ExternalProgramError(
|
|
128
|
+
program="qccodec",
|
|
129
|
+
message="Failed to parse CREST output.",
|
|
130
|
+
logs=stdout,
|
|
131
|
+
original_exception=e,
|
|
132
|
+
) from e
|
|
133
|
+
|
|
134
|
+
return results, stdout
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
3
|
+
from qcdata import FileInput, Files
|
|
4
|
+
|
|
5
|
+
from qccompute.adapters.base import BaseAdapter
|
|
6
|
+
|
|
7
|
+
from .utils import execute_subprocess
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FileAdapter(BaseAdapter[FileInput, Files]):
|
|
11
|
+
"""adapter for running a program on files."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, program: str) -> None:
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.program = program
|
|
16
|
+
|
|
17
|
+
def validate_input(self, input_data: FileInput) -> None:
|
|
18
|
+
"""No validation checks performed for FileAdapter"""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def compute_data(
|
|
22
|
+
self,
|
|
23
|
+
input_data: FileInput,
|
|
24
|
+
update_func: Callable | None = None,
|
|
25
|
+
update_interval: float | None = None,
|
|
26
|
+
**kwargs,
|
|
27
|
+
) -> tuple[Files, str]:
|
|
28
|
+
"""Compute the given program on the given files.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
input_data: The qcdata FileInput object for a computation.
|
|
32
|
+
update_func: A callback function to call as the program executes.
|
|
33
|
+
update_interval: The minimum time in seconds between calls to the
|
|
34
|
+
update_func.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Tuple of a `Files` object and the program output string for a
|
|
38
|
+
computation. The returned `Files` instance is initially empty and
|
|
39
|
+
will be populated with file data by the :meth:`.compute` method.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
ProgramNotFoundException: If the program is not found.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
stdout = execute_subprocess(
|
|
46
|
+
self.program, input_data.cmdline_args, update_func, update_interval
|
|
47
|
+
)
|
|
48
|
+
# Files will be added to this object by the .compute() method
|
|
49
|
+
return Files(), stdout
|