phenotypic.sdk_.slurm#
Shared SLURM utilities for array job chunking, sbatch submission, and dispatching.
This subpackage consolidates SLURM logic used by the main CLI
(phenotypic._cli).
Functions
Split images into array job chunks based on SLURM array size limits. |
|
Estimate max concurrent tasks from partition resources via sinfo. |
|
Generate |
|
Generate dispatcher scripts for a chain of chunk scripts. |
|
Generate a dispatcher script that submits the next chunk and dispatcher. |
|
Map any lifecycle generation to one collision-resistant path component. |
|
Query SLURM for MaxArraySize configuration. |
|
Query a conservative SLURM MaxSubmitJobs limit per user. |
|
Extract the SLURM job ID from sbatch output. |
|
Validate and canonicalize a SLURM time limit. |
|
Snapshot the caller's Python path under a SLURM-safe variable. |
|
Submit the first chunk and first dispatcher to start a drip-feed chain. |
|
Submit a script to SLURM via |
|
Validate that an array chunk is within acceptable bounds. |
|
Write |
Classes
Specification for a bash SLURM array script. |
- class phenotypic.sdk_.slurm.SlurmArrayScriptSpec(job_name: str, slurm_args: ~collections.abc.Mapping[str, ~typing.Any], log_path: ~pathlib.Path, task_indices: ~collections.abc.Sequence[int | str], body: str, error_log_path: ~pathlib.Path | None = None, prelude: str = '', comments: ~collections.abc.Sequence[str] = <factory>, array_name: str = 'TASK_INDICES', current_var: str = 'CURRENT_TASK_INDEX', missing_task_id_message: str = 'ERROR: SLURM_ARRAY_TASK_ID not set', bounds_error_message: str | None = None, signal_grace: int | None = None, requeue: bool = False)[source]
Bases:
objectSpecification for a bash SLURM array script.
- Parameters:
job_name (str) – SLURM job name for
#SBATCH --job-name.slurm_args (Mapping[str, Any]) – CLI-style SLURM arguments passed to the shared directive formatter.
log_path (Path) – Path used for both stdout and stderr logs.
error_log_path (Path | None) – Optional stderr path. Defaults to
log_path.task_indices (Sequence[int | str]) – Values mapped from
SLURM_ARRAY_TASK_IDinto the current task variable. Strings are shell-quoted.body (str) – Bash body executed once the current task variable is available.
prelude (str) – Optional bash block inserted after strict mode and before the task array.
comments (Sequence[str]) – Optional comment lines inserted after SBATCH directives.
array_name (str) – Bash array variable name.
current_var (str) – Bash variable that receives the current array entry.
missing_task_id_message (str) – Error printed when not running as an array job.
bounds_error_message (str | None) – Error printed when the array task id is out of bounds. When omitted, a message is derived from
array_name.signal_grace (int | None) – Optional seconds for
#SBATCH --signal=B:TERM@N.requeue (bool) – Whether to include
#SBATCH --requeue.
- array_name: str = 'TASK_INDICES'
- body: str
- current_var: str = 'CURRENT_TASK_INDEX'
- job_name: str
- log_path: Path
- missing_task_id_message: str = 'ERROR: SLURM_ARRAY_TASK_ID not set'
- prelude: str = ''
- requeue: bool = False
- phenotypic.sdk_.slurm.calculate_optimal_array_chunks(num_images: int, array_limit: int) List[Tuple[int, int]][source]
Split images into array job chunks based on SLURM array size limits.
Calculates the minimum number of array jobs needed to process all images while respecting the cluster’s MaxArraySize limit. Each chunk is represented as a (start, end) tuple of array indices.
- Parameters:
- Returns:
List of (start_idx, end_idx) tuples defining each array job chunk. Indices are 0-based and end is exclusive (Python slice convention).
- Raises:
ValueError – If
array_limitis not positive.- Return type:
Examples
>>> calculate_optimal_array_chunks(500, 1000) [(0, 500)]
>>> calculate_optimal_array_chunks(2500, 1000) [(0, 1000), (1000, 2000), (2000, 2500)]
>>> calculate_optimal_array_chunks(1000, 1000) [(0, 1000)]
>>> calculate_optimal_array_chunks(1001, 1000) [(0, 1000), (1000, 1001)]
- phenotypic.sdk_.slurm.estimate_concurrent_capacity(partition: str, cpus_per_task: int = 1, mem_gb_per_task: float = 4.0) int[source]
Estimate max concurrent tasks from partition resources via sinfo.
Queries SLURM’s
sinfofor the given partition to determine total CPUs, memory, and node count, then estimates how many tasks can run concurrently given per-task resource requirements.- Parameters:
- Returns:
Estimated number of concurrent tasks. Falls back to 100 if sinfo is unavailable.
- Return type:
Examples
>>> capacity = estimate_concurrent_capacity("compute") >>> capacity >= 1 # Always at least 1 True
- phenotypic.sdk_.slurm.format_sbatch_directives(job_name: str, slurm_args: Dict[str, Any], output_log: Path, error_log: Path) str[source]
Generate
#SBATCHdirective lines for a SLURM script.Converts CLI SLURM parameters to
#SBATCHdirectives with proper formatting. Reserved keys (array,output,error,job-name) are silently skipped because they are managed by the script generators.- Parameters:
- Returns:
String with all
#SBATCHdirectives joined by newlines.- Return type:
Notes
Time parameters (
time,slurm_time) as integers are treated as minutes and converted toHH:MM:SS.mem_gbis converted to--mem=<N>G.
- phenotypic.sdk_.slurm.generate_dispatcher_chain(chunk_scripts: List[Path], output_dir: Path, slurm_args: Dict[str, Any], log_dir: Path, finalizer_script: Path | None = None, continuation_dependency_kinds: Sequence[Literal['afterany', 'afterok']] | None = None, generation: str | None = None, lifecycle_output_dir: Path | None = None) List[Path][source]
Generate dispatcher scripts for a chain of chunk scripts.
For N chunk scripts, generates N-1 dispatcher scripts. Each dispatcher submits the next chunk and (if not last) the next dispatcher with the corresponding dependency kind on that chunk.
- Parameters:
chunk_scripts (List[Path]) – Ordered list of array job chunk script paths.
output_dir (Path) – Directory to write dispatcher scripts into.
slurm_args (Dict[str, Any]) – SLURM parameters dict (partition, etc.).
log_dir (Path) – Directory for dispatcher log files.
finalizer_script (Path | None) – Terminal finalizer passed only to the last dispatcher in the chain.
continuation_dependency_kinds (Sequence[Literal['afterany', 'afterok']] | None) – Dependency kind for every chunk-to-continuation edge. The sequence has
N - 1entries without a finalizer andNentries with one. Defaults toafteranyfor every edge.generation (str | None) – Optional explicit lifecycle generation. When supplied, dispatcher scripts are isolated under that generation.
lifecycle_output_dir (Path | None) – Optional lifecycle fence root when dispatcher scripts themselves are written beneath a separate control root.
- Returns:
List of dispatcher script paths (one fewer than
chunk_scripts, since the last chunk does not need a dispatcher). Empty if only one chunk exists.- Return type:
- phenotypic.sdk_.slurm.generate_dispatcher_script(next_chunk_script: Path, next_dispatcher_script: Path | None, output_path: Path, slurm_args: Dict[str, Any], log_dir: Path, *, output_dir: Path | None = None, generation: str | None = None, chunk_index: int = 1, finalizer_script: Path | None = None, dependency_kind: Literal['afterany', 'afterok'] = 'afterany') Path[source]
Generate a dispatcher script that submits the next chunk and dispatcher.
The dispatcher requests control-plane resources (1 CPU, 512M, 5 min) and invokes the Python lifecycle entry point. That entry point durably submits the next processing chunk and optional dependent dispatcher.
- Parameters:
next_chunk_script (Path) – Path to the next array job chunk script.
next_dispatcher_script (Path | None) – Path to the next dispatcher script, or
Nonefor the last chunk (no further dispatcher needed).output_path (Path) – Where to write the generated dispatcher script.
slurm_args (Dict[str, Any]) – SLURM parameters dict (used to extract partition).
log_dir (Path) – Directory for dispatcher log files.
output_dir (Path | None) – Base output directory containing the lifecycle state.
generation (str | None) – Exact scheduler generation for lifecycle submissions.
chunk_index (int) – Zero-based index of the chunk this dispatcher submits.
finalizer_script (Path | None) – Terminal finalizer submitted after the last chunk becomes terminal.
dependency_kind (Literal['afterany', 'afterok']) – Dependency condition for the continuation submitted after
next_chunk_script.
- Returns:
Path to the generated dispatcher script.
- Return type:
- phenotypic.sdk_.slurm.generation_script_key(generation: str) str[source]
Map any lifecycle generation to one collision-resistant path component.
- phenotypic.sdk_.slurm.get_slurm_array_limit() int[source]
Query SLURM for MaxArraySize configuration.
Uses
scontrol show configto retrieve the maximum number of array tasks allowed per job. Falls back to a conservative default if the query fails or SLURM is not available.- Returns:
1000).
- Return type:
Integer limit for array job size (default
Examples
>>> limit = get_slurm_array_limit() >>> limit >= 1000 # At least the default True
Notes
Result is cached for the session (lru_cache)
Default fallback is 1000 (conservative for most clusters)
Common SLURM values: 1001, 10000, 100000
- phenotypic.sdk_.slurm.get_slurm_max_submit_jobs() int | None[source]
Query a conservative SLURM MaxSubmitJobs limit per user.
Uses
sacctmgrto retrieve the maximum number of jobs a user can have in the queue simultaneously. This is typically set by QoS (Quality of Service) policies.- Returns:
Smallest configured positive QoS or user-association limit, or
Noneif no limit is configured or available. Taking the smallest value is conservative when the cluster exposes several QoS or association records and the caller cannot reliably infer which one the submitted job will use.- Return type:
int | None
Examples
>>> limit = get_slurm_max_submit_jobs() >>> limit is None or limit > 0 True
Notes
Result is cached for the session
Returns None if sacctmgr is not available
Returns None if no limit is configured (unlimited)
QoS and association records can carry different limits; returning the largest value can oversize an array for the active policy.
- phenotypic.sdk_.slurm.parse_job_id(sbatch_stdout: str) str[source]
Extract the SLURM job ID from sbatch output.
- Parameters:
sbatch_stdout (str) – Standard output from an
sbatchcommand, typically"Submitted batch job 12345\n".- Returns:
The job ID as a string.
- Raises:
RuntimeError – If the job ID cannot be parsed from the output.
- Return type:
Examples
>>> parse_job_id("Submitted batch job 12345\n") '12345'
- phenotypic.sdk_.slurm.parse_slurm_time(value: object) str | None[source]
Validate and canonicalize a SLURM time limit.
- Parameters:
value (object) – Empty input, positive integer minutes, or a SLURM duration in
HH:MM:SSorD-HH:MM:SSform.- Returns:
Canonical SLURM duration, or
Nonewhenvalueis empty.- Raises:
ValueError – If
valueis not one of the supported forms, contains an invalid clock field, or represents a nonpositive duration.- Return type:
str | None
Examples
>>> parse_slurm_time(90) '01:30:00' >>> parse_slurm_time("00:10:00") '00:10:00' >>> parse_slurm_time("1-04:00:00") '1-04:00:00'
- phenotypic.sdk_.slurm.sbatch_submission_environment(environment: Mapping[str, str] | None = None) dict[str, str][source]
Snapshot the caller’s Python path under a SLURM-safe variable.
Some clusters explicitly filter
PYTHONPATHeven whensbatchuses--export=ALL. PhenoTypic batch scripts restore this namespaced copy before invoking Python.
- phenotypic.sdk_.slurm.submit_drip_feed_start(chunk_scripts: List[Path], dispatcher_scripts: List[Path], *, finalizer_script: Path | None = None, continuation_dependency_kind: Literal['afterany', 'afterok'] = 'afterany', output_dir: Path | None = None, generation: str | None = None) Tuple[List[str], str | None][source]
Submit the first chunk and first dispatcher to start a drip-feed chain.
- Parameters:
chunk_scripts (List[Path]) – Ordered list of chunk script paths (must be non-empty).
dispatcher_scripts (List[Path]) – Dispatcher scripts from
generate_dispatcher_chain()(may be empty for single-chunk).finalizer_script (Path | None) – Terminal finalizer submitted after the only chunk when no dispatcher is required.
continuation_dependency_kind (Literal['afterany', 'afterok']) – Dependency condition for the initial dispatcher or single-chunk finalizer.
output_dir (Path | None) – Explicit lifecycle output root for attempt-scoped chains.
generation (str | None) – Explicit lifecycle generation for attempt-scoped chains.
- Returns:
Tuple of (job_ids, warning_message).
job_idscontains the submitted job IDs (1 or 2).warning_messageisNoneon success, or a string with recovery instructions if the dispatcher submission failed (chunk 0 was still submitted).- Raises:
RuntimeError – If the first chunk submission fails.
- Return type:
- phenotypic.sdk_.slurm.submit_script(script_path: Path, dependency_job_id: str | None = None, array_index: int | None = None) str[source]
Submit a script to SLURM via
sbatchand return the job ID.- Parameters:
- Returns:
SLURM job ID string.
- Raises:
RuntimeError – If
sbatchis not available, the submission fails, or the job ID cannot be parsed.- Return type:
- phenotypic.sdk_.slurm.validate_array_chunk(chunk: Tuple[int, int], num_images: int, array_limit: int) bool[source]
Validate that an array chunk is within acceptable bounds.
- Parameters:
- Returns:
True if chunk is valid, False otherwise.
- Return type:
Examples
>>> validate_array_chunk((0, 500), 1000, 1000) True
>>> validate_array_chunk((0, 1500), 1000, 1000) False
>>> validate_array_chunk((-1, 100), 1000, 1000) False
- phenotypic.sdk_.slurm.write_slurm_array_script(path: Path, spec: SlurmArrayScriptSpec) Path[source]
Write
spectopathand mark it executable.- Parameters:
path (Path) – Destination script path.
spec (SlurmArrayScriptSpec) – Script specification to render.
- Returns:
The destination path.
- Return type: