CodeEntropy 2.2.4__py3-none-any.whl → 2.4.1__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.
- CodeEntropy/__init__.py +1 -1
- CodeEntropy/config/argparse.py +232 -66
- CodeEntropy/config/runtime.py +18 -3
- CodeEntropy/core/dask_clusters.py +279 -0
- CodeEntropy/entropy/workflow.py +148 -89
- CodeEntropy/levels/axes.py +550 -55
- CodeEntropy/levels/dihedrals/__init__.py +0 -0
- CodeEntropy/levels/dihedrals/angle_observations.py +271 -0
- CodeEntropy/levels/dihedrals/conformational_state_builder.py +204 -0
- CodeEntropy/levels/dihedrals/kernels.py +116 -0
- CodeEntropy/levels/dihedrals/peak_detection.py +270 -0
- CodeEntropy/levels/dihedrals/state_assignment.py +281 -0
- CodeEntropy/levels/dihedrals/topology.py +233 -0
- CodeEntropy/levels/execution/__init__.py +0 -0
- CodeEntropy/levels/execution/chunks.py +28 -0
- CodeEntropy/levels/execution/policy.py +84 -0
- CodeEntropy/levels/execution/reducers.py +386 -0
- CodeEntropy/levels/execution/scheduler.py +306 -0
- CodeEntropy/levels/execution/tasks.py +330 -0
- CodeEntropy/levels/graph/__init__.py +0 -0
- CodeEntropy/levels/graph/conformation_dag.py +95 -0
- CodeEntropy/levels/graph/frame_dag.py +108 -0
- CodeEntropy/levels/graph/level_dag.py +206 -0
- CodeEntropy/levels/neighbors.py +144 -202
- CodeEntropy/levels/nodes/accumulators.py +13 -27
- CodeEntropy/levels/nodes/axes_topology.py +316 -0
- CodeEntropy/levels/nodes/covariance.py +240 -152
- CodeEntropy/levels/search.py +33 -37
- CodeEntropy/trajectory/__init__.py +0 -0
- CodeEntropy/trajectory/frames.py +134 -0
- CodeEntropy/{levels → trajectory}/mda.py +148 -92
- CodeEntropy/trajectory/source.py +49 -0
- {codeentropy-2.2.4.dist-info → codeentropy-2.4.1.dist-info}/METADATA +6 -5
- codeentropy-2.4.1.dist-info/RECORD +65 -0
- {codeentropy-2.2.4.dist-info → codeentropy-2.4.1.dist-info}/WHEEL +1 -1
- CodeEntropy/levels/dihedrals.py +0 -538
- CodeEntropy/levels/frame_dag.py +0 -116
- CodeEntropy/levels/level_dag.py +0 -331
- CodeEntropy/levels/nodes/conformations.py +0 -105
- CodeEntropy/levels/nodes/find_neighbors.py +0 -95
- codeentropy-2.2.4.dist-info/RECORD +0 -48
- {codeentropy-2.2.4.dist-info → codeentropy-2.4.1.dist-info}/entry_points.txt +0 -0
- {codeentropy-2.2.4.dist-info → codeentropy-2.4.1.dist-info}/licenses/LICENSE +0 -0
CodeEntropy/__init__.py
CHANGED
CodeEntropy/config/argparse.py
CHANGED
|
@@ -7,8 +7,10 @@ This module provides:
|
|
|
7
7
|
|
|
8
8
|
2) A `ConfigResolver` that:
|
|
9
9
|
|
|
10
|
-
- loads YAML configuration
|
|
11
|
-
- merges YAML values with CLI values
|
|
10
|
+
- loads YAML configuration if present,
|
|
11
|
+
- merges YAML values with CLI values, with CLI values taking precedence,
|
|
12
|
+
- fills missing defaults,
|
|
13
|
+
- auto-detects conda/mamba settings for HPC runs,
|
|
12
14
|
- adjusts logging verbosity,
|
|
13
15
|
- validates a subset of runtime inputs against the trajectory.
|
|
14
16
|
|
|
@@ -29,6 +31,8 @@ from typing import Any
|
|
|
29
31
|
|
|
30
32
|
import yaml
|
|
31
33
|
|
|
34
|
+
from CodeEntropy.core.dask_clusters import HPCDaskManager
|
|
35
|
+
|
|
32
36
|
logger = logging.getLogger(__name__)
|
|
33
37
|
|
|
34
38
|
|
|
@@ -39,10 +43,9 @@ class ArgSpec:
|
|
|
39
43
|
Attributes:
|
|
40
44
|
help: Help text shown in CLI usage.
|
|
41
45
|
default: Default value if not provided via CLI or YAML.
|
|
42
|
-
type: Python type for parsing
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
nargs: Optional nargs spec (e.g., "+").
|
|
46
|
+
type: Python type for parsing, such as int, float, str, or bool.
|
|
47
|
+
action: Optional argparse action, such as "store_true".
|
|
48
|
+
nargs: Optional nargs spec, such as "+".
|
|
46
49
|
"""
|
|
47
50
|
|
|
48
51
|
help: str
|
|
@@ -96,7 +99,7 @@ ARG_SPECS: dict[str, ArgSpec] = {
|
|
|
96
99
|
"step": ArgSpec(
|
|
97
100
|
type=int,
|
|
98
101
|
default=1,
|
|
99
|
-
help="Interval between two consecutive frames to be read
|
|
102
|
+
help="Interval between two consecutive frames to be read.",
|
|
100
103
|
),
|
|
101
104
|
"bin_width": ArgSpec(
|
|
102
105
|
type=int,
|
|
@@ -106,7 +109,7 @@ ARG_SPECS: dict[str, ArgSpec] = {
|
|
|
106
109
|
"temperature": ArgSpec(
|
|
107
110
|
type=float,
|
|
108
111
|
default=298.0,
|
|
109
|
-
help="Temperature for entropy calculation
|
|
112
|
+
help="Temperature for entropy calculation in K",
|
|
110
113
|
),
|
|
111
114
|
"verbose": ArgSpec(
|
|
112
115
|
action="store_true",
|
|
@@ -116,8 +119,7 @@ ARG_SPECS: dict[str, ArgSpec] = {
|
|
|
116
119
|
type=str,
|
|
117
120
|
default="output_file.json",
|
|
118
121
|
help=(
|
|
119
|
-
"Name of the output file to write results to
|
|
120
|
-
"to output_file.json"
|
|
122
|
+
"Name of the output file to write results to. Defaults to output_file.json."
|
|
121
123
|
),
|
|
122
124
|
),
|
|
123
125
|
"force_partitioning": ArgSpec(
|
|
@@ -138,35 +140,117 @@ ARG_SPECS: dict[str, ArgSpec] = {
|
|
|
138
140
|
"combined_forcetorque": ArgSpec(
|
|
139
141
|
type=bool,
|
|
140
142
|
default=True,
|
|
141
|
-
help="Use combined force-torque matrix for residue
|
|
143
|
+
help="Use combined force-torque matrix for residue-level vibrational entropies",
|
|
142
144
|
),
|
|
143
145
|
"customised_axes": ArgSpec(
|
|
144
146
|
type=bool,
|
|
145
147
|
default=True,
|
|
146
|
-
help="Use bonded axes to rotate forces for
|
|
148
|
+
help="Use bonded axes to rotate forces for united-atom vibrational entropies",
|
|
147
149
|
),
|
|
148
150
|
"search_type": ArgSpec(
|
|
149
151
|
type=str,
|
|
150
152
|
default="RAD",
|
|
151
|
-
help="Type of
|
|
152
|
-
"
|
|
153
|
+
help="Type of neighbour search to use. Default is RAD; grid search is also "
|
|
154
|
+
"available.",
|
|
155
|
+
),
|
|
156
|
+
"parallel_frames": ArgSpec(
|
|
157
|
+
type=bool,
|
|
158
|
+
default=False,
|
|
159
|
+
help="Execute frame-local covariance calculations in parallel using Dask.",
|
|
160
|
+
),
|
|
161
|
+
"use_dask": ArgSpec(
|
|
162
|
+
type=bool,
|
|
163
|
+
default=False,
|
|
164
|
+
help="Enable local Dask frame parallelism.",
|
|
165
|
+
),
|
|
166
|
+
"dask_workers": ArgSpec(
|
|
167
|
+
type=int,
|
|
168
|
+
default=None,
|
|
169
|
+
help="Number of local Dask worker processes.",
|
|
170
|
+
),
|
|
171
|
+
"dask_threads_per_worker": ArgSpec(
|
|
172
|
+
type=int,
|
|
173
|
+
default=1,
|
|
174
|
+
help="Threads per local Dask worker. Use 1 for MDAnalysis trajectory safety.",
|
|
175
|
+
),
|
|
176
|
+
"hpc": ArgSpec(
|
|
177
|
+
type=bool,
|
|
178
|
+
default=False,
|
|
179
|
+
help="Use a SLURM-backed Dask cluster for parallel frame execution.",
|
|
180
|
+
),
|
|
181
|
+
"hpc_account": ArgSpec(
|
|
182
|
+
type=str,
|
|
183
|
+
default=None,
|
|
184
|
+
help="SLURM account/project code.",
|
|
185
|
+
),
|
|
186
|
+
"hpc_qos": ArgSpec(
|
|
187
|
+
type=str,
|
|
188
|
+
default=None,
|
|
189
|
+
help="Optional SLURM QoS.",
|
|
190
|
+
),
|
|
191
|
+
"hpc_constraint": ArgSpec(
|
|
192
|
+
type=str,
|
|
193
|
+
default=None,
|
|
194
|
+
help="Optional SLURM node constraint.",
|
|
195
|
+
),
|
|
196
|
+
"hpc_queue": ArgSpec(
|
|
197
|
+
type=str,
|
|
198
|
+
default=None,
|
|
199
|
+
help="SLURM partition/queue.",
|
|
200
|
+
),
|
|
201
|
+
"hpc_cores": ArgSpec(
|
|
202
|
+
type=int,
|
|
203
|
+
default=1,
|
|
204
|
+
help="Number of CPU cores per Dask worker job.",
|
|
205
|
+
),
|
|
206
|
+
"hpc_processes": ArgSpec(
|
|
207
|
+
type=int,
|
|
208
|
+
default=1,
|
|
209
|
+
help="Number of Dask worker processes per SLURM job.",
|
|
210
|
+
),
|
|
211
|
+
"hpc_memory": ArgSpec(
|
|
212
|
+
type=str,
|
|
213
|
+
default="4GB",
|
|
214
|
+
help="Memory requested per Dask worker job.",
|
|
215
|
+
),
|
|
216
|
+
"hpc_walltime": ArgSpec(
|
|
217
|
+
type=str,
|
|
218
|
+
default="01:00:00",
|
|
219
|
+
help="Walltime for each Dask worker job, formatted as HH:MM:SS.",
|
|
220
|
+
),
|
|
221
|
+
"hpc_nodes": ArgSpec(
|
|
222
|
+
type=int,
|
|
223
|
+
default=1,
|
|
224
|
+
help="Number of SLURM Dask worker jobs to launch.",
|
|
225
|
+
),
|
|
226
|
+
"submit": ArgSpec(
|
|
227
|
+
type=bool,
|
|
228
|
+
default=False,
|
|
229
|
+
help="Submit a master SLURM job instead of running immediately.",
|
|
230
|
+
),
|
|
231
|
+
"conda_path": ArgSpec(
|
|
232
|
+
type=str,
|
|
233
|
+
default=None,
|
|
234
|
+
help="Path to conda executable used by the SLURM worker prologue.",
|
|
235
|
+
),
|
|
236
|
+
"conda_exec": ArgSpec(
|
|
237
|
+
type=str,
|
|
238
|
+
default=None,
|
|
239
|
+
help="Conda-compatible executable to use, usually conda or mamba.",
|
|
240
|
+
),
|
|
241
|
+
"conda_env": ArgSpec(
|
|
242
|
+
type=str,
|
|
243
|
+
default=None,
|
|
244
|
+
help="Conda environment name to activate on Dask workers.",
|
|
153
245
|
),
|
|
154
246
|
}
|
|
155
247
|
|
|
156
248
|
|
|
157
249
|
class ConfigResolver:
|
|
158
|
-
"""Load, merge, and validate CodeEntropy configuration.
|
|
159
|
-
|
|
160
|
-
This class provides a consistent interface for:
|
|
161
|
-
- YAML config discovery/loading
|
|
162
|
-
- CLI parser construction
|
|
163
|
-
- merging YAML values with CLI values (CLI wins)
|
|
164
|
-
- setting logging verbosity
|
|
165
|
-
- validating trajectory-related numeric parameters
|
|
166
|
-
"""
|
|
250
|
+
"""Load, merge, and validate CodeEntropy configuration."""
|
|
167
251
|
|
|
168
252
|
def __init__(self, arg_specs: dict[str, ArgSpec] | None = None) -> None:
|
|
169
|
-
"""
|
|
253
|
+
"""Initialise the resolver.
|
|
170
254
|
|
|
171
255
|
Args:
|
|
172
256
|
arg_specs: Optional override for argument specs. If omitted, uses
|
|
@@ -177,16 +261,11 @@ class ConfigResolver:
|
|
|
177
261
|
def load_config(self, directory_path: str) -> dict[str, Any]:
|
|
178
262
|
"""Load the first YAML config file found in a directory.
|
|
179
263
|
|
|
180
|
-
The current behavior matches your existing workflow:
|
|
181
|
-
- searches for ``*.yaml`` in `directory_path`,
|
|
182
|
-
- loads the first match,
|
|
183
|
-
- returns ``{"run1": {}}`` if none found or file is empty/invalid.
|
|
184
|
-
|
|
185
264
|
Args:
|
|
186
265
|
directory_path: Directory to search for YAML files.
|
|
187
266
|
|
|
188
267
|
Returns:
|
|
189
|
-
|
|
268
|
+
Configuration dictionary.
|
|
190
269
|
"""
|
|
191
270
|
yaml_files = glob.glob(os.path.join(directory_path, "*.yaml"))
|
|
192
271
|
if not yaml_files:
|
|
@@ -206,41 +285,38 @@ class ConfigResolver:
|
|
|
206
285
|
def str2bool(value: Any) -> bool:
|
|
207
286
|
"""Convert a string or boolean input into a boolean.
|
|
208
287
|
|
|
209
|
-
Accepts common string representations:
|
|
210
|
-
- True values: "true", "t", "yes", "1"
|
|
211
|
-
- False values: "false", "f", "no", "0"
|
|
212
|
-
|
|
213
|
-
If the input is already a boolean, it is returned as-is.
|
|
214
|
-
|
|
215
288
|
Args:
|
|
216
289
|
value: Input value to convert.
|
|
217
290
|
|
|
218
291
|
Returns:
|
|
219
|
-
|
|
292
|
+
Corresponding boolean value.
|
|
220
293
|
|
|
221
294
|
Raises:
|
|
222
295
|
argparse.ArgumentTypeError: If the input cannot be interpreted as a boolean.
|
|
223
296
|
"""
|
|
224
297
|
if isinstance(value, bool):
|
|
225
298
|
return value
|
|
299
|
+
|
|
226
300
|
if not isinstance(value, str):
|
|
227
|
-
raise argparse.ArgumentTypeError("Boolean value expected
|
|
301
|
+
raise argparse.ArgumentTypeError("Boolean value expected: true or false.")
|
|
228
302
|
|
|
229
303
|
lowered = value.lower()
|
|
230
|
-
if lowered in {"true", "t", "yes", "1"}:
|
|
304
|
+
if lowered in {"true", "t", "yes", "y", "1"}:
|
|
231
305
|
return True
|
|
232
|
-
|
|
306
|
+
|
|
307
|
+
if lowered in {"false", "f", "no", "n", "0"}:
|
|
233
308
|
return False
|
|
234
|
-
|
|
309
|
+
|
|
310
|
+
raise argparse.ArgumentTypeError("Boolean value expected: true or false.")
|
|
235
311
|
|
|
236
312
|
def build_parser(self) -> argparse.ArgumentParser:
|
|
237
|
-
"""Build an ArgumentParser from argument specs.
|
|
313
|
+
"""Build an ArgumentParser from the argument specs.
|
|
238
314
|
|
|
239
315
|
Returns:
|
|
240
|
-
|
|
316
|
+
Configured argparse.ArgumentParser.
|
|
241
317
|
"""
|
|
242
318
|
parser = argparse.ArgumentParser(
|
|
243
|
-
description="CodeEntropy:
|
|
319
|
+
description="CodeEntropy: entropy calculation with the MCC method."
|
|
244
320
|
)
|
|
245
321
|
|
|
246
322
|
for name, spec in self._arg_specs.items():
|
|
@@ -260,10 +336,13 @@ class ConfigResolver:
|
|
|
260
336
|
continue
|
|
261
337
|
|
|
262
338
|
kwargs: dict[str, Any] = {}
|
|
339
|
+
|
|
263
340
|
if spec.type is not None:
|
|
264
341
|
kwargs["type"] = spec.type
|
|
342
|
+
|
|
265
343
|
if spec.default is not None:
|
|
266
344
|
kwargs["default"] = spec.default
|
|
345
|
+
|
|
267
346
|
if spec.nargs is not None:
|
|
268
347
|
kwargs["nargs"] = spec.nargs
|
|
269
348
|
|
|
@@ -272,27 +351,31 @@ class ConfigResolver:
|
|
|
272
351
|
return parser
|
|
273
352
|
|
|
274
353
|
def resolve(
|
|
275
|
-
self,
|
|
354
|
+
self,
|
|
355
|
+
args: argparse.Namespace,
|
|
356
|
+
run_config: dict[str, Any] | None,
|
|
276
357
|
) -> argparse.Namespace:
|
|
277
|
-
"""Merge CLI arguments with YAML configuration and
|
|
358
|
+
"""Merge CLI arguments with YAML configuration and fill defaults.
|
|
278
359
|
|
|
279
360
|
Merge rule:
|
|
280
|
-
- CLI explicitly
|
|
281
|
-
- YAML values fill in
|
|
361
|
+
- CLI explicitly provided values take precedence.
|
|
362
|
+
- YAML values fill in values not provided by CLI.
|
|
282
363
|
- Defaults fill in anything still unset.
|
|
364
|
+
- HPC conda/mamba settings are auto-detected if missing.
|
|
283
365
|
|
|
284
366
|
Args:
|
|
285
367
|
args: Parsed CLI arguments.
|
|
286
368
|
run_config: Dict of YAML values for a specific run, or None.
|
|
287
369
|
|
|
288
370
|
Returns:
|
|
289
|
-
|
|
371
|
+
Mutated argparse.Namespace with merged values.
|
|
290
372
|
|
|
291
373
|
Raises:
|
|
292
374
|
TypeError: If `run_config` is not a dict or None.
|
|
293
375
|
"""
|
|
294
376
|
if run_config is None:
|
|
295
377
|
run_config = {}
|
|
378
|
+
|
|
296
379
|
if not isinstance(run_config, dict):
|
|
297
380
|
raise TypeError("run_config must be a dictionary or None.")
|
|
298
381
|
|
|
@@ -306,15 +389,17 @@ class ConfigResolver:
|
|
|
306
389
|
|
|
307
390
|
self._apply_yaml_defaults(args, run_config, cli_provided)
|
|
308
391
|
self._ensure_defaults(args)
|
|
392
|
+
self._apply_hpc_conda_auto_detection(args)
|
|
309
393
|
self._apply_logging_level(bool(getattr(args, "verbose", False)))
|
|
310
394
|
|
|
311
395
|
return args
|
|
312
396
|
|
|
313
397
|
@staticmethod
|
|
314
398
|
def _detect_cli_overrides(
|
|
315
|
-
args_dict: dict[str, Any],
|
|
399
|
+
args_dict: dict[str, Any],
|
|
400
|
+
default_dict: dict[str, Any],
|
|
316
401
|
) -> set[str]:
|
|
317
|
-
"""Detect which args were explicitly overridden
|
|
402
|
+
"""Detect which args were explicitly overridden on the CLI.
|
|
318
403
|
|
|
319
404
|
Args:
|
|
320
405
|
args_dict: Parsed arg values.
|
|
@@ -323,7 +408,9 @@ class ConfigResolver:
|
|
|
323
408
|
Returns:
|
|
324
409
|
Set of argument names that differ from defaults.
|
|
325
410
|
"""
|
|
326
|
-
return {
|
|
411
|
+
return {
|
|
412
|
+
key for key, value in args_dict.items() if value != default_dict.get(key)
|
|
413
|
+
}
|
|
327
414
|
|
|
328
415
|
def _apply_yaml_defaults(
|
|
329
416
|
self,
|
|
@@ -334,27 +421,44 @@ class ConfigResolver:
|
|
|
334
421
|
"""Apply YAML values onto args for keys not provided by CLI.
|
|
335
422
|
|
|
336
423
|
Args:
|
|
337
|
-
args: Parsed CLI arguments
|
|
424
|
+
args: Parsed CLI arguments, mutated in place.
|
|
338
425
|
run_config: YAML dict for this run.
|
|
339
426
|
cli_provided: Keys explicitly set via CLI.
|
|
340
427
|
"""
|
|
341
428
|
for key, yaml_value in run_config.items():
|
|
342
429
|
if yaml_value is None or key in cli_provided:
|
|
343
430
|
continue
|
|
431
|
+
|
|
344
432
|
if key in self._arg_specs:
|
|
345
|
-
logger.debug(
|
|
433
|
+
logger.debug("Using YAML value for %s: %s", key, yaml_value)
|
|
346
434
|
setattr(args, key, yaml_value)
|
|
347
435
|
|
|
348
436
|
def _ensure_defaults(self, args: argparse.Namespace) -> None:
|
|
349
437
|
"""Ensure all known args have defaults if still unset.
|
|
350
438
|
|
|
351
439
|
Args:
|
|
352
|
-
args: Parsed arg namespace
|
|
440
|
+
args: Parsed arg namespace, mutated in place.
|
|
353
441
|
"""
|
|
354
442
|
for key, spec in self._arg_specs.items():
|
|
355
443
|
if getattr(args, key, None) is None:
|
|
356
444
|
setattr(args, key, spec.default)
|
|
357
445
|
|
|
446
|
+
@staticmethod
|
|
447
|
+
def _apply_hpc_conda_auto_detection(args: argparse.Namespace) -> None:
|
|
448
|
+
"""Auto-detect conda/mamba settings for HPC runs if not configured.
|
|
449
|
+
|
|
450
|
+
This runs during resolution, before validation, so strict validation can
|
|
451
|
+
still require conda_env, conda_exec, and conda_path after detection.
|
|
452
|
+
"""
|
|
453
|
+
using_hpc = bool(getattr(args, "hpc", False))
|
|
454
|
+
submitting = bool(getattr(args, "submit", False))
|
|
455
|
+
|
|
456
|
+
if not using_hpc and not submitting:
|
|
457
|
+
return
|
|
458
|
+
|
|
459
|
+
manager = HPCDaskManager(args)
|
|
460
|
+
manager.resolve_conda_settings()
|
|
461
|
+
|
|
358
462
|
@staticmethod
|
|
359
463
|
def _apply_logging_level(verbose: bool) -> None:
|
|
360
464
|
"""Adjust logging levels for this module's logger and its handlers.
|
|
@@ -364,8 +468,10 @@ class ConfigResolver:
|
|
|
364
468
|
"""
|
|
365
469
|
level = logging.DEBUG if verbose else logging.INFO
|
|
366
470
|
logger.setLevel(level)
|
|
471
|
+
|
|
367
472
|
for handler in logger.handlers:
|
|
368
473
|
handler.setLevel(level)
|
|
474
|
+
|
|
369
475
|
if verbose:
|
|
370
476
|
logger.debug("Verbose mode enabled. Logger set to DEBUG level.")
|
|
371
477
|
|
|
@@ -373,8 +479,8 @@ class ConfigResolver:
|
|
|
373
479
|
"""Validate user inputs against sensible runtime constraints.
|
|
374
480
|
|
|
375
481
|
Args:
|
|
376
|
-
u: MDAnalysis
|
|
377
|
-
args: Parsed
|
|
482
|
+
u: MDAnalysis Universe or compatible object with a trajectory.
|
|
483
|
+
args: Parsed and merged arguments.
|
|
378
484
|
|
|
379
485
|
Raises:
|
|
380
486
|
ValueError: If a parameter is invalid.
|
|
@@ -385,11 +491,13 @@ class ConfigResolver:
|
|
|
385
491
|
self._check_input_bin_width(args)
|
|
386
492
|
self._check_input_temperature(args)
|
|
387
493
|
self._check_input_force_partitioning(args)
|
|
494
|
+
self._check_parallel_frame_options(args)
|
|
388
495
|
|
|
389
496
|
@staticmethod
|
|
390
497
|
def _check_input_start(u: Any, args: argparse.Namespace) -> None:
|
|
391
498
|
"""Check that the start index does not exceed the trajectory length."""
|
|
392
499
|
traj_len = len(u.trajectory)
|
|
500
|
+
|
|
393
501
|
if args.start > traj_len:
|
|
394
502
|
raise ValueError(
|
|
395
503
|
f"Invalid 'start' value: {args.start}. It exceeds the trajectory "
|
|
@@ -400,10 +508,11 @@ class ConfigResolver:
|
|
|
400
508
|
def _check_input_end(u: Any, args: argparse.Namespace) -> None:
|
|
401
509
|
"""Check that the end index does not exceed the trajectory length."""
|
|
402
510
|
traj_len = len(u.trajectory)
|
|
511
|
+
|
|
403
512
|
if args.end > traj_len:
|
|
404
513
|
raise ValueError(
|
|
405
|
-
f"Invalid 'end' value: {args.end}. It exceeds the trajectory length
|
|
406
|
-
f"{traj_len}."
|
|
514
|
+
f"Invalid 'end' value: {args.end}. It exceeds the trajectory length "
|
|
515
|
+
f"of {traj_len}."
|
|
407
516
|
)
|
|
408
517
|
|
|
409
518
|
@staticmethod
|
|
@@ -421,25 +530,82 @@ class ConfigResolver:
|
|
|
421
530
|
"""Check that the bin width is within the valid range [0, 360]."""
|
|
422
531
|
if args.bin_width < 0 or args.bin_width > 360:
|
|
423
532
|
raise ValueError(
|
|
424
|
-
f"Invalid 'bin_width': {args.bin_width}. It must be between
|
|
425
|
-
f"degrees."
|
|
533
|
+
f"Invalid 'bin_width': {args.bin_width}. It must be between "
|
|
534
|
+
f"0 and 360 degrees."
|
|
426
535
|
)
|
|
427
536
|
|
|
428
537
|
@staticmethod
|
|
429
538
|
def _check_input_temperature(args: argparse.Namespace) -> None:
|
|
430
|
-
"""Check that the temperature is
|
|
431
|
-
if args.temperature
|
|
539
|
+
"""Check that the temperature is positive."""
|
|
540
|
+
if args.temperature <= 0:
|
|
432
541
|
raise ValueError(
|
|
433
|
-
f"Invalid 'temperature': {args.temperature}. Temperature
|
|
434
|
-
f"
|
|
542
|
+
f"Invalid 'temperature': {args.temperature}. Temperature must be "
|
|
543
|
+
f"greater than 0 K."
|
|
435
544
|
)
|
|
436
545
|
|
|
437
546
|
def _check_input_force_partitioning(self, args: argparse.Namespace) -> None:
|
|
438
|
-
"""Warn if force partitioning
|
|
547
|
+
"""Warn if force partitioning differs from the default value."""
|
|
439
548
|
default_value = self._arg_specs["force_partitioning"].default
|
|
549
|
+
|
|
440
550
|
if args.force_partitioning != default_value:
|
|
441
551
|
logger.warning(
|
|
442
552
|
"'force_partitioning' is set to %s, which differs from the default %s.",
|
|
443
553
|
args.force_partitioning,
|
|
444
554
|
default_value,
|
|
445
555
|
)
|
|
556
|
+
|
|
557
|
+
@staticmethod
|
|
558
|
+
def _check_parallel_frame_options(args: argparse.Namespace) -> None:
|
|
559
|
+
"""Validate local Dask, HPC Dask, and submit-related options."""
|
|
560
|
+
dask_workers = getattr(args, "dask_workers", None)
|
|
561
|
+
if dask_workers is not None and dask_workers < 1:
|
|
562
|
+
raise ValueError("'dask_workers' must be at least 1 if provided.")
|
|
563
|
+
|
|
564
|
+
dask_threads = getattr(args, "dask_threads_per_worker", 1)
|
|
565
|
+
if dask_threads < 1:
|
|
566
|
+
raise ValueError("'dask_threads_per_worker' must be at least 1.")
|
|
567
|
+
|
|
568
|
+
using_hpc = bool(getattr(args, "hpc", False))
|
|
569
|
+
submitting = bool(getattr(args, "submit", False))
|
|
570
|
+
|
|
571
|
+
if submitting and not using_hpc:
|
|
572
|
+
raise ValueError("'submit' requires 'hpc' to be enabled.")
|
|
573
|
+
|
|
574
|
+
if not using_hpc and not submitting:
|
|
575
|
+
return
|
|
576
|
+
|
|
577
|
+
if not getattr(args, "hpc_queue", None):
|
|
578
|
+
raise ValueError("'hpc_queue' must be provided when using HPC Dask.")
|
|
579
|
+
|
|
580
|
+
if getattr(args, "hpc_nodes", 1) < 1:
|
|
581
|
+
raise ValueError("'hpc_nodes' must be at least 1.")
|
|
582
|
+
|
|
583
|
+
if getattr(args, "hpc_cores", 1) < 1:
|
|
584
|
+
raise ValueError("'hpc_cores' must be at least 1.")
|
|
585
|
+
|
|
586
|
+
if getattr(args, "hpc_processes", 1) < 1:
|
|
587
|
+
raise ValueError("'hpc_processes' must be at least 1.")
|
|
588
|
+
|
|
589
|
+
if not getattr(args, "hpc_memory", None):
|
|
590
|
+
raise ValueError("'hpc_memory' must be provided when using HPC Dask.")
|
|
591
|
+
|
|
592
|
+
if not getattr(args, "hpc_walltime", None):
|
|
593
|
+
raise ValueError("'hpc_walltime' must be provided when using HPC Dask.")
|
|
594
|
+
|
|
595
|
+
if not getattr(args, "conda_env", None):
|
|
596
|
+
raise ValueError(
|
|
597
|
+
"'conda_env' must be provided when using HPC Dask, or be detectable "
|
|
598
|
+
"from the active conda/mamba environment."
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
if not getattr(args, "conda_path", None):
|
|
602
|
+
raise ValueError(
|
|
603
|
+
"'conda_path' must be provided when using HPC Dask, or be detectable "
|
|
604
|
+
"from the active conda/mamba environment."
|
|
605
|
+
)
|
|
606
|
+
|
|
607
|
+
if not getattr(args, "conda_exec", None):
|
|
608
|
+
raise ValueError(
|
|
609
|
+
"'conda_exec' must be provided when using HPC Dask, or be detectable "
|
|
610
|
+
"from the active conda/mamba environment."
|
|
611
|
+
)
|
CodeEntropy/config/runtime.py
CHANGED
|
@@ -33,12 +33,15 @@ from rich.table import Table
|
|
|
33
33
|
from rich.text import Text
|
|
34
34
|
|
|
35
35
|
from CodeEntropy.config.argparse import ConfigResolver
|
|
36
|
+
from CodeEntropy.core.dask_clusters import HPCDaskManager
|
|
36
37
|
from CodeEntropy.core.logging import LoggingConfig
|
|
37
38
|
from CodeEntropy.entropy.workflow import EntropyWorkflow
|
|
38
|
-
from CodeEntropy.levels.dihedrals import
|
|
39
|
-
|
|
39
|
+
from CodeEntropy.levels.dihedrals.conformational_state_builder import (
|
|
40
|
+
ConformationStateBuilder,
|
|
41
|
+
)
|
|
40
42
|
from CodeEntropy.molecules.grouping import MoleculeGrouper
|
|
41
43
|
from CodeEntropy.results.reporter import ResultsReporter
|
|
44
|
+
from CodeEntropy.trajectory.mda import UniverseOperations
|
|
42
45
|
|
|
43
46
|
logger = logging.getLogger(__name__)
|
|
44
47
|
console = LoggingConfig.get_console()
|
|
@@ -223,8 +226,9 @@ class CodeEntropyRunner:
|
|
|
223
226
|
|
|
224
227
|
This method:
|
|
225
228
|
- Sets up logging and prints the splash screen
|
|
226
|
-
- Loads YAML
|
|
229
|
+
- Loads YAML configuration from CWD and parses CLI args
|
|
227
230
|
- Merges args with YAML per-run config
|
|
231
|
+
- Optionally submits a master SLURM job and exits
|
|
228
232
|
- Builds the MDAnalysis Universe (with optional force merging)
|
|
229
233
|
- Validates user parameters
|
|
230
234
|
- Constructs dependencies and executes EntropyWorkflow
|
|
@@ -256,6 +260,16 @@ class CodeEntropyRunner:
|
|
|
256
260
|
|
|
257
261
|
args = self._config_manager.resolve(args, run_config)
|
|
258
262
|
|
|
263
|
+
if getattr(args, "submit", False):
|
|
264
|
+
if os.environ.get("CODEENTROPY_SUBMITTED_JOB") == "1":
|
|
265
|
+
run_logger.info(
|
|
266
|
+
"Already running inside submitted SLURM job; "
|
|
267
|
+
"continuing workflow."
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
HPCDaskManager(args).submit_master()
|
|
271
|
+
return
|
|
272
|
+
|
|
259
273
|
log_level = (
|
|
260
274
|
logging.DEBUG if getattr(args, "verbose", False) else logging.INFO
|
|
261
275
|
)
|
|
@@ -298,6 +312,7 @@ class CodeEntropyRunner:
|
|
|
298
312
|
except Exception:
|
|
299
313
|
logger.error("Run arguments at failure could not be serialized")
|
|
300
314
|
|
|
315
|
+
logger.exception("Fatal error during entropy calculation")
|
|
301
316
|
raise RuntimeError("CodeEntropyRunner encountered an error") from exc
|
|
302
317
|
|
|
303
318
|
@staticmethod
|