Skip to content

TeraChem

qccompute.adapters.terachem.TeraChemAdapter

Adapter for TeraChem.

supported_calctypes = [CalcType.energy, CalcType.gradient, CalcType.hessian, CalcType.optimization] class-attribute instance-attribute

Supported calculation types.

program_version(stdout: str | None = None) -> str | None

Get the program version.

Parameters:

Name Type Description Default
stdout str | None

The stdout from the program.

None

Returns:

Type Description
str | None

The program version.

Source code in src/qccompute/adapters/terachem.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def program_version(self, stdout: str | None = None) -> str | None:
    """Get the program version.

    Args:
        stdout: The stdout from the program.

    Returns:
        The program version.
    """
    if stdout:
        try:
            return parse_version(stdout)
        except qccodec_exceptions.ParserError:
            # If the version string is not found. Happens when libcuda.so is not
            # found and TeraChem fails to start. terachem --version will fail too.
            return None
    else:
        try:
            return execute_subprocess(self.program, ["--version"])[17:]
        except ExternalProgramError:
            return None

compute_data(input_data: ProgramInput, update_func: Callable | None = None, update_interval: float | None = None, **kwargs) -> tuple[SinglePointData | OptimizationData, str]

Execute TeraChem on the given input.

Parameters:

Name Type Description Default
input_data ProgramInput

The qcdata ProgramInput object for a computation.

required
update_func Callable | None

A callback function to call as the program executes.

None
update_interval float | None

The minimum time in seconds between calls to the update_func.

None

Returns:

Type Description
tuple[SinglePointData | OptimizationData, str]

A tuple of SinglePointData and the stdout str.

Source code in src/qccompute/adapters/terachem.py
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def compute_data(
    self,
    input_data: ProgramInput,
    update_func: Callable | None = None,
    update_interval: float | None = None,
    **kwargs,
) -> tuple[SinglePointData | OptimizationData, str]:
    """Execute TeraChem on the given input.

    Args:
        input_data: The qcdata ProgramInput object for a computation.
        update_func: A callback function to call as the program executes.
        update_interval: The minimum time in seconds between calls to the
            update_func.

    Returns:
        A tuple of SinglePointData and the stdout str.
    """
    # Construct TeraChem native input files
    try:
        native_input = qccodec.encode(input_data)
    except qccodec.exceptions.EncoderError as e:
        raise AdapterInputError(program=self.program) from e

    # Write the input files to disk
    input_filename = "tc.in"
    Path(input_filename).write_text(native_input.input_file)
    Path(native_input.geometry_filename).write_text(native_input.geometry_file)

    # Execute TeraChem
    execution_error: ExternalProgramError | None = None
    try:
        stdout = execute_subprocess(
            self.program,
            [input_filename, *input_data.cmdline_args],
            update_func,
            update_interval,
        )
    except ProgramNotFoundError:
        raise  # Nothing ran, so there are no program artifacts to decode.
    except ExternalProgramError as exc:
        execution_error = exc
        stdout = exc.logs or ""

    # Get the scratch output directory
    parent = Path.cwd()
    # TeraChem creates a directory named scr<xyz_filename> in the current working
    scr_dir = next(parent.glob("scr*"), None)
    if scr_dir is None and execution_error is None:
        execution_error = ExternalProgramError(
            self.program,
            f"TeraChem did not create a 'scr' directory in {parent}.",
            logs=stdout,
        )

    # Parse output
    try:
        results = qccodec.decode(
            self.program,
            input_data.calctype,
            stdout=stdout,
            directory=scr_dir,
            input_data=input_data,
            failed=execution_error is not None,
        )
    except qccodec_exceptions.ParserError as e:
        if execution_error is not None:
            execution_error.data = e.data
            raise execution_error from e
        raise ExternalProgramError(
            program="qccodec",
            message="Failed to parse TeraChem output.",
            data=e.data,
            logs=stdout,
            original_exception=e,
        ) from e
    if execution_error is not None:
        execution_error.data = results
        raise execution_error
    return results, stdout

collect_wfn() -> dict[str, str | bytes]

Append wavefunction data to the output.

Source code in src/qccompute/adapters/terachem.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def collect_wfn(self) -> dict[str, str | bytes]:
    """Append wavefunction data to the output."""

    # Naming conventions from TeraChem uses xyz filename as scratch dir postfix
    scr_dir = next(Path().glob("scr*"), None)
    if not scr_dir:
        raise AdapterError(
            "No scratch directory found for wavefunction collection."
        )
    # Wavefunction filenames
    wfn_filenames = ("c0", "ca0", "cb0")
    wfn_paths = [scr_dir / fn for fn in wfn_filenames]
    if not any(wfn_path.exists() for wfn_path in wfn_paths):
        raise AdapterError(f"No wavefunction files found in {Path.cwd()}")

    wfns: dict[str, str | bytes] = {}
    for wfn_path in wfn_paths:
        if wfn_path.exists():
            wfns[str(wfn_path)] = wfn_path.read_bytes()
    return wfns

propagate_wfn(output: ProgramOutput[ProgramInput, SinglePointData], program_input: ProgramInput) -> ProgramInput

Propagate the wavefunction from the previous calculation.

Parameters:

Name Type Description Default
output ProgramOutput[ProgramInput, SinglePointData]

The output from a previous calculation containing wavefunction data.

required
program_input ProgramInput

The ProgramInput object on which to place the wavefunction data.

required

Returns:

Type Description
ProgramInput

A new input containing the propagated wavefunction and guess keyword.

Source code in src/qccompute/adapters/terachem.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
def propagate_wfn(
    self,
    output: ProgramOutput[ProgramInput, SinglePointData],
    program_input: ProgramInput,
) -> ProgramInput:
    """Propagate the wavefunction from the previous calculation.

    Args:
        output: The output from a previous calculation containing wavefunction data.
        program_input: The ProgramInput object on which to place the wavefunction data.

    Returns:
        A new input containing the propagated wavefunction and guess keyword.
    """

    values = program_input.model_dump()

    # Wavefunction filenames
    suffixes = ("c0", "ca0", "cb0")
    matches = {}
    for k, v in output.results.files.items():
        for suffix in suffixes:
            if k.endswith(suffix):
                matches[suffix] = v

    if not "c0" in matches and not ("ca0" in matches and "cb0" in matches):
        raise AdapterInputError(
            program=self.program,
            message="Could not find c0 or ca/b0 files in output.",
        )

    # Load wavefunction data onto ProgramInput object

    if "c0" in matches:
        values["files"]["c0"] = matches["c0"]
        values["keywords"]["guess"] = "c0"

    else:  # ca0_bytes and cb0_bytes
        assert "ca0" in matches and "cb0" in matches  # for mypy
        values["files"]["ca0"] = matches["ca0"]
        values["files"]["cb0"] = matches["cb0"]
        values["keywords"]["guess"] = "ca0 cb0"

    return ProgramInput.model_validate(values)

qccompute.adapters.terachem_fe.TeraChemFEAdapter()

Adapter for TeraChem's Protocol Buffer Server and Frontend file server.

Source code in src/qccompute/adapters/terachem_fe.py
23
24
25
26
27
def __init__(self):
    super().__init__()
    # Check that a compatible tcpb is installed.
    self.tcpb = self._ensure_tcpb()
    self.client = self.tcpb.TCFrontEndClient

supported_calctypes = [CalcType.energy, CalcType.gradient] class-attribute instance-attribute

Supported calculation types.

program_version(stdout: str | None = None) -> str | None

Program version is not available via the PB server.

Source code in src/qccompute/adapters/terachem_fe.py
54
55
56
def program_version(self, stdout: str | None = None) -> str | None:
    """Program version is not available via the PB server."""
    return None

compute_data(input_data: ProgramInput, update_func: Callable | None = None, update_interval: float | None = None, **kwargs) -> tuple[SinglePointData, str]

Execute TeraChem on the given input.

Parameters:

Name Type Description Default
input_data ProgramInput

The qcdata ProgramInput object for a computation.

required
update_func Callable | None

A callback function to call as the program executes.

None
update_interval float | None

The minimum time in seconds between calls to the update_func.

None

Returns:

Type Description
tuple[SinglePointData, str]

A tuple of SinglePointData and the stdout str.

Source code in src/qccompute/adapters/terachem_fe.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def compute_data(
    self,
    input_data: ProgramInput,
    update_func: Callable | None = None,
    update_interval: float | None = None,
    **kwargs,
) -> tuple[SinglePointData, str]:
    """Execute TeraChem on the given input.

    Args:
        input_data: The qcdata ProgramInput object for a computation.
        update_func: A callback function to call as the program executes.
        update_interval: The minimum time in seconds between calls to the
            update_func.

    Returns:
        A tuple of SinglePointData and the stdout str.
    """
    try:
        with self.client() as client:
            prog_output = client.compute(input_data)
    except self.tcpb.exceptions.TCPBError as e:
        exc = ExternalProgramError(
            program=self.program,
            # Pass logs to .compute() via the exception
            # Will only exist for TeraChemFrontendAdapter
            logs=e.program_output.logs if e.program_output is not None else None,
            data=e.program_output.results if e.program_output is not None else None,
        )

        raise exc

    else:
        # Write files to disk to be collected by BaseAdapter.compute()
        # Used only for TeraChemFrontendAdapter
        prog_output.results.save_files()

    return prog_output.results, prog_output.logs

qccompute.adapters.terachem_pbs.TeraChemPBSAdapter()

Adapter for TeraChem's Protocol Buffer Server.

Source code in src/qccompute/adapters/terachem_pbs.py
 9
10
11
12
13
def __init__(self):
    super().__init__()
    # Check that tcpb is installed.
    self.tcpb = self._ensure_tcpb()
    self.client = self.tcpb.TCProtobufClient

TeraChem FE/PBS dependency

The optional FE/PBS adapters require a separate tcpb migration. Published tcpb==0.16.0 constructs the old qcdata models internally, so requests now report an explicit dependency error (or return a failed ProgramOutput when raise_exc=False). Standard TeraChem subprocess execution uses the migrated qccodec parsers and is independent of this limitation.