gpuma 0.5.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.
gpuma/__init__.py ADDED
@@ -0,0 +1,65 @@
1
+ """GPUMA: A minimal package for molecular geometry optimization using Fairchem's UMA models.
2
+
3
+ This package provides essential tools for:
4
+ - Single molecule optimization from SMILES strings or XYZ files
5
+ - Batch optimization of multiple structures (e.g., conformer ensembles)
6
+ - Format conversion between SMILES and XYZ coordinates
7
+ - Configurable optimization parameters through JSON/YAML configuration files
8
+
9
+ The package is designed to be simple and focused, providing only the functionality
10
+ that is actually implemented and tested.
11
+ """
12
+
13
+ from .api import (
14
+ optimize_batch_multi_xyz_file,
15
+ optimize_batch_xyz_directory,
16
+ optimize_ensemble_smiles,
17
+ optimize_single_smiles,
18
+ optimize_single_xyz_file,
19
+ )
20
+ from .config import Config, default_config, load_config_from_file, save_config_to_file
21
+ from .decorators import time_it
22
+ from .io_handler import (
23
+ read_multi_xyz,
24
+ read_xyz,
25
+ read_xyz_directory,
26
+ save_multi_xyz,
27
+ save_xyz_file,
28
+ smiles_to_ensemble,
29
+ smiles_to_xyz,
30
+ )
31
+ from .models import load_model_fairchem, load_model_torchsim
32
+ from .optimizer import optimize_single_structure, optimize_structure_batch
33
+ from .structure import Structure
34
+
35
+ __all__ = [
36
+ # Data types
37
+ "Structure",
38
+ # I/O functions
39
+ "read_xyz",
40
+ "read_multi_xyz",
41
+ "read_xyz_directory",
42
+ "smiles_to_xyz",
43
+ "smiles_to_ensemble",
44
+ "save_xyz_file",
45
+ "save_multi_xyz",
46
+ # Optimization functions
47
+ "optimize_single_structure",
48
+ "optimize_structure_batch",
49
+ # Convenience functions (re-exported from ``api``)
50
+ "optimize_single_smiles",
51
+ "optimize_single_xyz_file",
52
+ "optimize_ensemble_smiles",
53
+ "optimize_batch_multi_xyz_file",
54
+ "optimize_batch_xyz_directory",
55
+ # Model functions
56
+ "load_model_torchsim",
57
+ "load_model_fairchem",
58
+ # Configuration
59
+ "Config",
60
+ "default_config",
61
+ "load_config_from_file",
62
+ "save_config_to_file",
63
+ # Decorators
64
+ "time_it",
65
+ ]
gpuma/api.py ADDED
@@ -0,0 +1,280 @@
1
+ """Public high-level Python API for common geometry optimization workflows.
2
+
3
+ This module provides convenience functions built on top of the lower-level
4
+ I/O and optimization utilities. It allows users to easily optimize molecular
5
+ structures starting from SMILES strings or XYZ files, as well as optimizing
6
+ ensembles of conformers.
7
+
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import cast
13
+
14
+ from .config import Config, load_config_from_file
15
+ from .io_handler import (
16
+ file_exists,
17
+ read_multi_xyz,
18
+ read_xyz,
19
+ read_xyz_directory,
20
+ save_multi_xyz,
21
+ save_xyz_file,
22
+ smiles_to_ensemble,
23
+ smiles_to_xyz,
24
+ )
25
+ from .optimizer import optimize_single_structure, optimize_structure_batch
26
+ from .structure import Structure
27
+
28
+
29
+ def optimize_single_smiles(
30
+ smiles: str,
31
+ output_file: str | None = None,
32
+ config: Config | None = None,
33
+ ) -> Structure:
34
+ """Optimize a single molecule from a SMILES string.
35
+
36
+ This function uses the provided SMILES string to generate an initial 3D structure
37
+ using the Morfeus library. It then optimizes the structure using the specified
38
+ optimization pipeline.
39
+
40
+ Args:
41
+ smiles (str): SMILES string of the molecule to optimize.
42
+ output_file (str): Path to an output XYZ file where the optimized structure
43
+ will be written. If None, the optimized structure is not saved to a file.
44
+ config (Config, optional): Config object to control the optimization pipeline.
45
+ Highly recommended to specify. If None, the configuration will be loaded
46
+ from the default file.
47
+
48
+ Returns:
49
+ Structure: The optimized molecular structure as a Structure object.
50
+
51
+ Raises:
52
+ ValueError: If the generated structure is not valid.
53
+
54
+ """
55
+ if config is None:
56
+ config = load_config_from_file()
57
+
58
+ multiplicity = getattr(config.optimization, "multiplicity", 1)
59
+ structure = smiles_to_xyz(smiles, multiplicity=multiplicity)
60
+
61
+ if not isinstance(structure, Structure):
62
+ raise ValueError("smiles_to_xyz did not return a Structure")
63
+ structure.comment = f"Optimized from SMILES: {smiles}"
64
+ result = optimize_single_structure(structure, config)
65
+
66
+ if output_file:
67
+ save_xyz_file(result, output_file)
68
+
69
+ return result
70
+
71
+
72
+ def optimize_single_xyz_file(
73
+ input_file: str,
74
+ output_file: str | None = None,
75
+ config: Config | None = None,
76
+ ) -> Structure:
77
+ """Optimize a single structure from an XYZ file.
78
+
79
+ This function reads a molecular structure from the specified XYZ file,
80
+ optimizes it using the provided optimization pipeline, and optionally
81
+ writes the optimized structure to an output XYZ file.
82
+
83
+ Args:
84
+ input_file (str): Path to an input XYZ file from which to read the initial structure.
85
+ output_file (str): Path to an output XYZ file where the optimized structure will be written.
86
+ If None, the optimized structure will not be saved to a file.
87
+ config (Config, optional): Config object to control the optimization pipeline.
88
+ Highly recommended to specify. If None, the configuration will be loaded from
89
+ the default file.
90
+
91
+ Returns:
92
+ Structure: The optimized molecular structure as a Structure object.
93
+
94
+ Raises:
95
+ ValueError: If the input file does not exist or if the read structure is not valid.
96
+
97
+ """
98
+ if not file_exists(input_file):
99
+ raise ValueError(f"Input file {input_file} does not exist.")
100
+ if config is None:
101
+ config = load_config_from_file()
102
+
103
+ eff_charge = int(getattr(config.optimization, "charge", 0))
104
+ eff_mult = int(getattr(config.optimization, "multiplicity", 1))
105
+ structure = read_xyz(input_file, charge=eff_charge, multiplicity=eff_mult)
106
+ if not isinstance(structure, Structure):
107
+ raise ValueError("read_xyz did not return a Structure")
108
+ structure.comment = f"Optimized from: {input_file}"
109
+ result = optimize_single_structure(structure, config)
110
+
111
+ if output_file:
112
+ save_xyz_file(result, output_file)
113
+
114
+ return result
115
+
116
+
117
+ def optimize_ensemble_smiles(
118
+ smiles: str,
119
+ output_file: str | None = None,
120
+ config: Config | None = None,
121
+ ) -> list[Structure]:
122
+ """Optimize a conformer ensemble generated from a SMILES string.
123
+
124
+ This function generates a specified number of conformers from the provided
125
+ SMILES string using the Morfeus library. It then optimizes each conformer
126
+ using the specified optimization pipeline. Optionally, the optimized ensemble
127
+ can be saved to a multi-structure XYZ file.
128
+
129
+ Args:
130
+ smiles (str): SMILES string of the molecule for which to generate conformers.
131
+ output_file (str, optional): Path to an output multi-structure XYZ file where
132
+ the optimized ensemble will be written. If None, the ensemble is not saved to a file.
133
+ config (Config, optional): Config object to control the optimization pipeline.
134
+ Highly recommended to specify. If None, the configuration will be loaded from
135
+ the default file.
136
+
137
+ Returns:
138
+ list[Structure]: A list of optimized molecular structures as Structure objects.
139
+
140
+ Raises:
141
+ ValueError: If output_file is not specified when required or if the generated
142
+ conformers are not valid.
143
+
144
+ """
145
+ if config is None:
146
+ config = load_config_from_file()
147
+ multiplicity = int(getattr(config.optimization, "multiplicity", 1))
148
+ num_conformers = int(getattr(config.optimization, "max_num_conformers", 10))
149
+ conformers = smiles_to_ensemble(smiles, num_conformers, multiplicity)
150
+ if not isinstance(conformers, list) or (
151
+ len(conformers) and not isinstance(conformers[0], Structure)
152
+ ):
153
+ raise ValueError("smiles_to_ensemble did not return a list of Structure")
154
+ for s in conformers:
155
+ s.multiplicity = multiplicity
156
+ results = optimize_structure_batch(conformers, config)
157
+
158
+ if output_file:
159
+ comments = [
160
+ f"Optimized conformer {i + 1} from SMILES: {smiles}" for i in range(len(results))
161
+ ]
162
+ save_multi_xyz(results, output_file, comments)
163
+
164
+ return results
165
+
166
+ def optimize_batch_multi_xyz_file(
167
+ input_file: str,
168
+ output_file: str | None = None,
169
+ config: Config | None = None,
170
+ ) -> list[Structure]:
171
+ """Optimize a batch of structures from a multi-structure XYZ file.
172
+
173
+ This function reads multiple molecular structures from the specified multi-structure
174
+ XYZ file, optimizes each structure using the provided optimization pipeline, and writes
175
+ the optimized structures to an output multi-structure XYZ file.
176
+
177
+ Args:
178
+ input_file (str): Path to an input multi-structure XYZ file from which to
179
+ read the initial structures.
180
+ output_file (str): Path to an output multi-structure XYZ file where the optimized
181
+ structures will be written.
182
+ If None, the optimized structures will not be saved to a file.
183
+ config (Config, optional): Config object to control the optimization pipeline.
184
+ Highly recommended to specify. If None, the configuration will be loaded from
185
+ the default file.
186
+
187
+ Returns:
188
+ list[Structure]: A list of optimized molecular structures as Structure objects.
189
+
190
+ Raises:
191
+ ValueError: If the input file does not exist or if the read structures are not valid.
192
+
193
+ """
194
+ if not file_exists(input_file):
195
+ raise ValueError(f"Input file {input_file} does not exist.")
196
+
197
+ if config is None:
198
+ config = load_config_from_file()
199
+
200
+ eff_charge = int(getattr(config.optimization, "charge", 0))
201
+ eff_mult = int(getattr(config.optimization, "multiplicity", 1))
202
+
203
+ structures = cast(
204
+ list[Structure],
205
+ read_multi_xyz(input_file, charge=eff_charge, multiplicity=eff_mult),
206
+ )
207
+ if not isinstance(structures, list) or (
208
+ len(structures) and not isinstance(structures[0], Structure)
209
+ ):
210
+ raise ValueError("read_multi_xyz did not return a list of Structure")
211
+
212
+ results = optimize_structure_batch(structures, config)
213
+
214
+ if output_file:
215
+ comments = [
216
+ f"Optimized structure {i + 1} from: {input_file}" for i in range(len(results))
217
+ ]
218
+ save_multi_xyz(results, output_file, comments)
219
+
220
+ return results
221
+
222
+ def optimize_batch_xyz_directory(
223
+ input_directory: str,
224
+ output_file: str,
225
+ config: Config | None = None,
226
+ ) -> list[Structure]:
227
+ """Optimize a batch of structures from XYZ files in a directory.
228
+
229
+ This function reads multiple molecular structures from XYZ files in the specified input
230
+ directory, optimizes each structure using the provided optimization pipeline,
231
+ and writes the optimized structures to XYZ files in the specified output directory.
232
+
233
+ Args:
234
+ input_directory (str): Path to an input directory containing XYZ files.
235
+ output_file (str): Path to an output multi-structure XYZ file where the
236
+ optimized structures will be written.
237
+ config (Config, optional): Config object to control the optimization pipeline.
238
+ Highly recommended to specify. If None, the configuration will be loaded from the
239
+ default file.
240
+
241
+ Returns:
242
+ list[Structure]: A list of optimized molecular structures as Structure objects.
243
+
244
+ Raises:
245
+ ValueError: If the input directory does not exist or contains no valid XYZ files.
246
+
247
+ """
248
+ if config is None:
249
+ config = load_config_from_file()
250
+
251
+ eff_charge = int(getattr(config.optimization, "charge", 0))
252
+ eff_mult = int(getattr(config.optimization, "multiplicity", 1))
253
+
254
+ structures = cast(
255
+ list[Structure],
256
+ read_xyz_directory(
257
+ input_directory,
258
+ charge=eff_charge,
259
+ multiplicity=eff_mult,
260
+ ),
261
+ )
262
+
263
+ results = optimize_structure_batch(structures, config)
264
+
265
+ if output_file:
266
+ comments = [
267
+ f"Optimized structure {i + 1} from batch input"
268
+ for i in range(len(results))
269
+ ]
270
+ save_multi_xyz(results, output_file, comments)
271
+
272
+ return results
273
+
274
+ __all__ = [
275
+ "optimize_single_smiles",
276
+ "optimize_single_xyz_file",
277
+ "optimize_ensemble_smiles",
278
+ "optimize_batch_multi_xyz_file",
279
+ "optimize_batch_xyz_directory",
280
+ ]