API Reference

Everything here is importable from the top-level package, e.g. from dask_setup import setup_dask_client. Helpers backed by optional dependencies (xarray, dask-jobqueue, zarr) raise an ImportError naming the install command when the dependency is missing.

Cluster setup

dask_setup.client.setup_dask_client(workload_type: str | None = None, max_workers: int | None = None, reserve_mem_gb: float | None = None, max_mem_gb: float | None = None, dashboard: bool | None = None, adaptive: bool | None = None, min_workers: int | None = None, profile: str | None = None, suggest_chunks: bool | None = None, config: DaskSetupConfig | None = None, ds: None = None, fallback_on_detection_failure: bool = False, adaptive_memory: bool = False, mode: str = 'interactive', multi_node_config: MultiNodeConfig | None = None) → tuple[Client, LocalCluster, str][source]
dask_setup.client.setup_dask_client(workload_type: str | None = None, max_workers: int | None = None, reserve_mem_gb: float | None = None, max_mem_gb: float | None = None, dashboard: bool | None = None, adaptive: bool | None = None, min_workers: int | None = None, profile: str | None = None, suggest_chunks: bool | None = None, config: DaskSetupConfig | None = None, ds: xr.Dataset | xr.DataArray = None, fallback_on_detection_failure: bool = False, adaptive_memory: bool = False, mode: str = 'interactive', multi_node_config: MultiNodeConfig | None = None) → tuple[Client, LocalCluster, str, dict[str, int]]

Create a single-node Dask LocalCluster tuned for HPC login/compute nodes.

Routes temp/spill to $PBS_JOBFS when present.

Every configuration parameter below defaults to None, meaning “not supplied”. Values are resolved in this order, highest priority first:

  1. Parameters you pass explicitly here

  2. profile=

  3. config=

  4. DaskSetupConfig field defaults

A parameter you leave at None keeps whatever the profile or config object specifies; a parameter you pass always wins, even when the value happens to equal the library default.

Parameters:
  • workload_type ({"cpu","io","mixed","gpu","auto"} or None) – Shape worker topology for CPU-bound, I/O-bound, or mixed workloads. None inherits from the profile/config, falling back to "io".

  • max_workers (int or None) – Cap on worker processes. Defaults to all logical cores available.

  • reserve_mem_gb (float or None) – Memory to reserve for OS / cache / filesystem (GiB). None inherits from the profile/config, falling back to a machine-aware default: 20% of total RAM, clamped to [4.0, 50.0] GiB.

  • max_mem_gb (float or None) – Cap total memory used by Dask. Default is node total.

  • dashboard (bool or None) – If True, start a dashboard on a random free port and print an SSH tunnel hint. None inherits, falling back to True.

  • adaptive (bool or None) – Enable single-node adaptive scaling (elastic number of workers). None inherits, falling back to False.

  • min_workers (int or None) – Minimum workers when adaptive=True.

  • profile (str or None) – Name of configuration profile to use. Profile settings override config= and are in turn overridden by explicit parameters. Pass "auto" to select a profile from the detected resources.

  • suggest_chunks (bool) – If True, print xarray chunking recommendations after cluster setup. When ds= is also provided, the recommendations are computed from the actual dataset. Without ds=, generic guidance is printed. Requires xarray and numpy to be installed.

  • config (DaskSetupConfig or None) –

    A pre-built configuration object supplying every field at once. This is the recommended way to avoid the default-value ambiguity that occurs with individual keyword parameters.

    The full precedence, lowest to highest, is:

    library defaults  <  config= or profile=  <  explicit keywords
    

    config= and profile= occupy the same layer rather than stacking: passing both is allowed, but the profile supplies the base and the config object is ignored. Use explicit keyword arguments for anything you want on top.

  • ds (xr.Dataset, xr.DataArray, or None) –

    Optional xarray dataset. When provided:

    • Chunk validation is run automatically (warnings emitted for dangerously large or very small existing chunks).

    • Chunk recommendations are computed for the specific dataset and cluster configuration, and returned as the fourth element of the return tuple.

    • If suggest_chunks=True, the recommendations are also printed.

    Requires xarray and numpy to be installed.

  • fallback_on_detection_failure (bool) – If True, use conservative hardcoded defaults (2 cores, 8 GiB) when all resource detection methods fail, instead of raising ResourceDetectionError. A warning is logged. Default False preserves the existing behaviour of raising on failure.

  • adaptive_memory (bool) – If True, call tune_memory_thresholds() once after the cluster is ready. This reads the (initially zero) spill stats and tightens the worker memory.target / memory.spill thresholds slightly, giving workers more head-room from the start. Default False.

  • mode ({"interactive", "auto", "local", "pbs", "slurm"}) –

    Backend selection.

    • "interactive" (default) — use resources already allocated in the current PBS or SLURM job, interactive (qsub -I, salloc) or batch. Never submits new jobs. Multi-node allocations create an SSHCluster across all nodes in PBS_NODEFILE / SLURM_NODELIST; a single node, or no allocation at all, gets the same LocalCluster as "local".

    • "local" — always use a single-node LocalCluster (default behaviour prior to v2.0).

    • "pbs" — launch via dask-jobqueue.PBSCluster (submits new batch jobs). Requires pip install dask-jobqueue.

    • "slurm" — launch via dask-jobqueue.SLURMCluster (submits new batch jobs).

    • "auto" — inspect the environment and choose "interactive" when inside a PBS interactive job (PBS_ENVIRONMENT=PBS_INTERACTIVE) or a SLURM interactive allocation (SLURM_BATCH_FLAG not set to "1"); "pbs" when PBS_JOBID is set in a batch job; "slurm" when SLURM_JOB_ID is set in a batch job; "local" otherwise.

  • multi_node_config (MultiNodeConfig or None) – Configuration for the multi-node backend (mode="pbs" or "slurm"). Ignored when mode="local". When None and a multi-node mode is selected, a default MultiNodeConfig is constructed from workload_type.

Returns:

  • When ds is None (default) – (client, cluster, dask_local_dir) — a 3-tuple.

  • When ds is provided – (client, cluster, dask_local_dir, chunks) — a 4-tuple, where chunks is a dict[str, int] of recommended chunk sizes for that dataset (can be passed directly to ds.chunk(chunks)).

Raises:
Return type:

tuple[Client, LocalCluster, str] | tuple[Client, LocalCluster, str, dict[str, int]]

See also

DaskClientContext

Context manager version that closes the cluster on exit.

validate_chunks

Validate existing chunking against cluster memory limits.

recommend_chunks

Standalone chunk recommendation function.

rechunk_dataset

Rechunk a dataset to new target chunk sizes.

Examples

# Basic usage (3-tuple)
client, cluster, tmp = setup_dask_client(workload_type="io")

# Basic usage (3-tuple)
client, cluster, tmp = setup_dask_client(workload_type="io")

# With dataset — chunk validation + recommendations (4-tuple)
ds = xr.open_zarr("era5.zarr")
client, cluster, tmp, chunks = setup_dask_client(ds=ds, suggest_chunks=True)
ds_opt = ds.chunk(chunks)

# Multi-node PBS — submits worker jobs via dask-jobqueue
client, cluster, tmp = setup_dask_client(
    mode="pbs",
    multi_node_config=MultiNodeConfig(
        workers_per_node=4,
        cores_per_worker=12,
        mem_per_worker_gb=32.0,
        walltime="04:00:00",
    ),
)
class dask_setup.client.DaskClientContext(**kwargs)[source]

Bases: object

Context manager wrapper for setup_dask_client().

Ensures the Dask client and cluster are closed cleanly on exit — even when an exception is raised inside the with block.

All keyword arguments accepted by setup_dask_client() are valid.

When a dataset is passed via ds=, the context manager yields a 4-tuple (client, cluster, tmp, chunks) where chunks is the recommended chunk dictionary for that dataset. Without ds=, the familiar 3-tuple (client, cluster, tmp) is yielded.

Examples

from dask_setup import DaskClientContext

# 3-tuple form (no dataset)
with DaskClientContext(workload_type="cpu") as (client, cluster, tmp):
    result = client.compute(ds.mean())

# 4-tuple form (with dataset — chunks auto-computed)
with DaskClientContext(ds=my_ds, suggest_chunks=True) as (client, cluster, tmp, chunks):
    ds_opt = my_ds.chunk(chunks)
    result = client.compute(ds_opt.mean())

The context manager does not delete the spill/temp directory on exit so that users can inspect spill artefacts for debugging if needed.

Parameters:

kwargs (Any)

Configuration and profiles

class dask_setup.config.DaskSetupConfig(workload_type='io', max_workers=None, reserve_mem_gb=50.0, max_mem_gb=None, dashboard=True, adaptive=False, min_workers=None, temp_base_dir=None, dashboard_port=None, silence_logs=False, memory_target=0.75, memory_spill=0.85, memory_pause=0.92, memory_terminate=0.98, spill_compression='auto', comm_compression=False, spill_threads=None, suggest_chunks=False, io_format=None, io_target_chunk_mb=(128, 512), io_access_pattern='auto', io_storage_location='auto', io_compression_level=None, fallback_on_detection_failure=False, adaptive_memory=False, name='', description='', tags=<factory>)[source]

Bases: object

Configuration for dask_setup with validation and defaults.

This class encapsulates all configuration parameters for setup_dask_client(), provides validation, and supports serialization for profile management.

Parameters:
  • workload_type (str)

  • max_workers (int | None)

  • reserve_mem_gb (float)

  • max_mem_gb (float | None)

  • dashboard (bool)

  • adaptive (bool)

  • min_workers (int | None)

  • temp_base_dir (str | None)

  • dashboard_port (int | None)

  • silence_logs (bool)

  • memory_target (float)

  • memory_spill (float)

  • memory_pause (float)

  • memory_terminate (float)

  • spill_compression (str)

  • comm_compression (bool)

  • spill_threads (int | None)

  • suggest_chunks (bool)

  • io_format (str | None)

  • io_target_chunk_mb (tuple[float, float])

  • io_access_pattern (str)

  • io_storage_location (str)

  • io_compression_level (int | None)

  • fallback_on_detection_failure (bool)

  • adaptive_memory (bool)

  • name (str)

  • description (str)

  • tags (list[str])

validate()[source]

Validate all configuration parameters.

Raises:

ConfigurationValidationError – If any parameter is invalid

Return type:

None

to_dict()[source]

Convert configuration to dictionary for serialization.

Return type:

dict[str, Any]

classmethod from_dict(data, skip_validation=False)[source]

Create configuration from dictionary.

Parameters:
  • data (dict[str, Any]) – Dictionary with configuration parameters

  • skip_validation (bool) – If True, skip validation during creation

Returns:

DaskSetupConfig instance

Return type:

DaskSetupConfig

merge_overrides(overrides)[source]

Apply a dict of explicitly-named field overrides on top of this configuration.

Unlike merge_with(), only the keys actually present in overrides are applied. This is the right tool for merging caller-supplied keyword arguments: building a throwaway DaskSetupConfig from them would also carry ~20 untouched dataclass defaults, which would silently overwrite the profile or config object being merged into.

Parameters:

overrides (dict[str, Any] | None) – Mapping of field name -> value. Only these fields are changed. None or an empty dict is a no-op. Unknown keys are ignored (matching from_dict()). A tags entry replaces the tag list rather than merging it.

Returns:

New merged configuration, validated as a whole.

Return type:

DaskSetupConfig

merge_with(other)[source]

Merge this configuration with another, with other taking precedence.

Every non-None field of other is applied — including fields left at their dataclass defaults. That makes this the right tool for layering two fully-specified configurations (defaults < profile), and the wrong one for applying a handful of caller keyword arguments; use merge_overrides() for that.

Parameters:

other (DaskSetupConfig | None) – Configuration to merge (higher precedence)

Returns:

New merged configuration

Return type:

DaskSetupConfig

get_setup_client_kwargs()[source]

Get parameters for setup_dask_client() function.

Returns:

Dictionary of parameters that can be passed to setup_dask_client()

Return type:

dict[str, Any]

validate_against_environment()[source]

Validate configuration against current environment.

Returns:

List of warning messages about potentially problematic settings

Return type:

list[str]

class dask_setup.config_manager.ConfigManager(config_dir=None, site_profiles_dir=None)[source]

Bases: object

Manages loading and saving of configuration profiles.

Parameters:
  • config_dir (Path | str | None)

  • site_profiles_dir (Path | str | None)

ensure_config_dir()[source]

Ensure configuration directory exists.

Return type:

None

list_profiles()[source]

List all available profiles (builtin + site-wide + user).

Profiles are merged in precedence order — user profiles overwrite site-wide profiles, which overwrite builtins of the same name.

Returns:

Dictionary of profile name -> ConfigProfile

Return type:

dict[str, ConfigProfile]

get_profile(name, _inheritance_chain=None)[source]

Get a specific profile by name.

Precedence: user profiles → site-wide profiles → builtins, so a profile you save shadows a builtin of the same name. This matches list_profiles(), and therefore what dask-setup list and dask-setup show display.

Parameters:
  • name (str) – Profile name

  • _inheritance_chain (tuple[str, ...] | None) – Internal. Names already being resolved further up a based_on chain, used for cycle detection. Callers should not set this.

Returns:

ConfigProfile if found, None otherwise

Return type:

ConfigProfile | None

save_profile(profile)[source]

Save a profile to disk.

The current PROFILE_FORMAT_VERSION is always written to the version field so that future versions of dask_setup can detect and warn about profiles created by newer releases.

Parameters:

profile (ConfigProfile) – Profile to save

Raises:

InvalidConfigurationError – If trying to save builtin profile or validation fails

Return type:

None

delete_profile(name)[source]

Delete a user profile.

Parameters:

name (str) – Profile name to delete

Returns:

True if deleted, False if not found

Raises:

InvalidConfigurationError – If trying to delete builtin profile

Return type:

bool

load_profile_from_file(file_path, _inheritance_chain=None)[source]

Load a profile from a YAML file, resolving any based_on inheritance.

Parameters:
  • file_path (Path) – Path to the YAML profile file.

  • _inheritance_chain (tuple[str, ...] | None) – Internal parameter used to detect circular inheritance chains. Callers should not set this.

Returns:

Fully resolved profile (with inherited config already merged in).

Return type:

ConfigProfile

Raises:

InvalidConfigurationError – If the file is unreadable, contains invalid YAML, references a non-existent parent, or has a circular based_on chain.

validate_profile(name)[source]

Validate a profile and return results.

Parameters:

name (str) – Profile name

Returns:

Tuple of (is_valid, errors, warnings)

Return type:

tuple[bool, list[str], list[str]]

import_profile_from_url(url, name_override=None, force=False)[source]

Fetch a YAML profile from url and install it as a user profile.

Parameters:
  • url (str) – HTTP/HTTPS URL that returns a raw YAML profile file.

  • name_override (str | None) – If provided, the profile is saved under this name instead of the name embedded in the YAML file.

  • force (bool) – If True, overwrite an existing profile of the same name. If False (default), raise InvalidConfigurationError when the target name already exists.

Returns:

The imported (and saved) profile.

Return type:

ConfigProfile

Raises:

InvalidConfigurationError – If the URL cannot be fetched, the content is invalid, or the profile name already exists and force is False.

static get_profile_schema()[source]

Return the JSON Schema for the profile YAML format.

The schema can be used by editors (VS Code, PyCharm, etc.) for autocompletion and inline validation of .yaml profile files.

Returns:

A JSON Schema (draft-07) describing the profile YAML structure.

Return type:

dict

auto_select_profile(resources=None)[source]

Select the most appropriate builtin profile for the current environment.

Inspects PBS/SLURM environment variables, detected resources, and the runtime environment (e.g. Jupyter) to pick a named profile.

This is called automatically when profile="auto" is passed to setup_dask_client().

Parameters:

resources (ResourceSpec or None) – Detected system resources. When None, the method falls back to environment variable inspection only.

Returns:

Name of the selected builtin profile. Always one of the builtin profile names (guaranteed to exist in builtin_profiles).

Return type:

str

Notes

Selection logic (first match wins):

  1. Jupyter environment → "interactive"

  2. Small machine (≤ 8 cores or ≤ 16 GiB total RAM) → "development"

  3. I/O-heavy PBS job ($PBS_JOBFS set, ≥ 16 cores) → "zarr_io_heavy"

  4. Large climate/science HPC job (≥ 48 cores or ≥ 128 GiB) → "climate_analysis"

  5. General HPC job with substantial resources → "production"

  6. Fallback → "development"

create_profile_interactively(name)[source]

Create a new profile interactively via CLI prompts.

Parameters:

name (str) – Profile name

Returns:

Created ConfigProfile

Return type:

ConfigProfile

Multi-node

class dask_setup.multinode.MultiNodeConfig(workload_type='cpu', workers_per_node=1, cores_per_worker=1, mem_per_worker_gb=4.0, walltime='01:00:00', queue='normal', project=None, job_extra_directives=<factory>, n_nodes=1, shared_tmp_dir=None, env_extra=<factory>, scheduler_options=<factory>, reserve_mem_gb=4.0, adaptive=False, min_jobs=1, max_jobs=10)[source]

Bases: object

Configuration for a multi-node dask-jobqueue cluster.

This is a companion to DaskSetupConfig that adds the fields needed to submit worker jobs to PBS or SLURM. The single-node DaskSetupConfig fields (workload_type, reserve_mem_gb, etc.) are accepted here too so callers can use one object for both paths.

Parameters:
  • workload_type (str) – Worker topology — "cpu", "io", "mixed", or "gpu". Passed through to the single-node topology logic when deciding cores_per_worker and threads_per_worker.

  • workers_per_node (int) – Number of Dask worker processes to start per submitted job. Typically 1 per GPU for GPU workloads, or ncpus / cores_per_worker for CPU workloads.

  • cores_per_worker (int) – CPU cores allocated to each worker. For PBS this maps to -l ncpus=cores_per_worker * workers_per_node.

  • mem_per_worker_gb (float) – RAM in GiB to request per worker process. The total node request is mem_per_worker_gb * workers_per_node.

  • walltime (str) – Job walltime string in HH:MM:SS format.

  • queue (str) – Scheduler queue / partition to submit to.

  • project (str | None) – PBS/SLURM project / account string (maps to -P / --account).

  • job_extra_directives (list[str]) – Additional #PBS or #SBATCH directives to inject verbatim into the job script header.

  • n_nodes (int) – Number of nodes to request. Defaults to 1 (scale up by adding more jobs, not more nodes per job).

  • shared_tmp_dir (str | pathlib.Path | None) – Path to a Lustre / GPFS shared directory for stores that must be visible to all workers (e.g. Rechunker targets). When None, per-node temp is used.

  • env_extra (list[str]) – Extra shell lines inserted verbatim near the top of worker job scripts, before the worker starts – e.g. ["module load conda/analysis3", "export OMP_NUM_THREADS=1"]. Write the full statement including any export; the lines are not modified. This matches dask-jobqueue’s job_script_prologue, which is where they end up.

  • scheduler_options (dict[str, Any]) – Options forwarded verbatim to dask-jobqueue’s scheduler_options parameter.

  • reserve_mem_gb (float) – OS memory reservation per node (for compatibility with single-node config objects). Defaults to 4.0 in multi-node mode (the OS footprint on a dedicated compute node is small).

  • adaptive (bool) – Enable adaptive (elastic) scaling via cluster.adapt(minimum=min_jobs, maximum=max_jobs).

  • min_jobs (int) – Minimum active jobs when adaptive is True.

  • max_jobs (int) – Maximum active jobs when adaptive is True.

property total_cores_per_job: int

Total CPU cores requested per submitted job.

property total_mem_gb_per_job: float

Total RAM (GiB) requested per submitted job.

to_dict()[source]

Return a JSON-serialisable representation.

Return type:

dict[str, Any]

class dask_setup.multinode.SharedTempDir(path, create_subdirectory=True, cleanup_on_close=False)[source]

Bases: object

A shared temporary directory visible to all nodes in the job.

On multi-node PBS/SLURM jobs, $PBS_JOBFS is per-node and cannot be used as a shared store (e.g. for Rechunker targets). This helper wraps a Lustre / GPFS / Scratch path that is visible cluster-wide.

Parameters:
  • path (str | pathlib.Path) – Absolute path to the shared filesystem directory to use.

  • create_subdirectory (bool) – If True (default), create a unique dask_tmp_<jobid> subdirectory inside path to avoid collisions across concurrent jobs.

  • cleanup_on_close (bool) – If True, delete the directory tree when cleanup() is called. Defaults to False — shared scratch is typically cleaned by the scheduler.

resolved_path

The actual directory that was created (may include the subdirectory).

Type:

Path

cleanup()[source]

Remove the temporary directory if cleanup_on_close is True.

Return type:

None

dask_setup.multinode.detect_cluster_mode()[source]

Detect the appropriate cluster mode from the execution environment.

Distinguishes between batch jobs (where submitting child worker jobs via dask-jobqueue is the right approach) and interactive jobs (where the resources are already allocated and workers should be started directly).

PBS sets PBS_ENVIRONMENT=PBS_INTERACTIVE for interactive jobs (qsub -I). SLURM sets SLURM_JOB_NAME=interactive or the job was submitted via salloc (SLURM_JOB_ID is set but SLURM_BATCH_FLAG is absent or 0).

Returns:

One of "pbs", "slurm", "interactive", or "local".

"interactive" is returned when inside a PBS interactive job (PBS_ENVIRONMENT=PBS_INTERACTIVE) or a SLURM interactive allocation (salloc / SLURM_BATCH_FLAG not set). In this mode, setup_interactive_cluster() should be used instead of setup_pbs_cluster() / setup_slurm_cluster().

Return type:

str

dask_setup.multinode.setup_interactive_cluster(workload_type='cpu', workers_per_node=None, threads_per_worker=None, wait_for_workers=True, worker_timeout=60.0, scheduler_port=8786, config=None)[source]

Set up a Dask cluster using resources already allocated in an interactive job.

This is the right function to call from a Jupyter notebook running inside a qsub -I (PBS) or salloc (SLURM) interactive session. Unlike setup_pbs_cluster() / setup_slurm_cluster(), it does not submit new batch jobs — it creates workers directly on the nodes that PBS or SLURM has already assigned to the current session.

Behaviour

  • Single allocated node — creates a LocalCluster using all cores and memory available in the allocation (reads PBS_NCPUS / PBS_MEM or SLURM equivalents via detect_resources()).

  • Multiple allocated nodes — creates a dask.distributed.SSHCluster across all nodes listed in PBS_NODEFILE / $SLURM_NODELIST. Passwordless SSH between nodes is required (standard on most HPC systems).

param workload_type:

Worker topology — "cpu", "io", "mixed", or "auto". Determines threads per worker and workers per node using the same logic as single-node setup_dask_client().

param workers_per_node:

Override the number of worker processes to start per node. When None (default), derived from workload_type and the core count.

param threads_per_worker:

Override threads per worker process. When None, derived from workload_type.

param wait_for_workers:

Block until at least one worker has connected. Defaults to True.

param worker_timeout:

Seconds to wait for workers. Defaults to 60 s (workers start almost immediately because no job submission is involved).

param scheduler_port:

Port for the Dask scheduler (multi-node SSH path only).

returns:

client — a connected dask.distributed.Client. cluster — LocalCluster or SSHCluster. tmp_path — path to the temporary directory, or "" when not set.

rtype:

(client, cluster, tmp_path)

Parameters:
  • workload_type (str)

  • workers_per_node (int | None)

  • threads_per_worker (int | None)

  • wait_for_workers (bool)

  • worker_timeout (float)

  • scheduler_port (int)

  • config (Any)

Return type:

tuple[Any, Any, str | None]

dask_setup.multinode.setup_pbs_cluster(config=None, *, workload_type=None, workers_per_node=None, cores_per_worker=None, mem_per_worker_gb=None, walltime=None, queue=None, project=None, job_extra_directives=None, n_nodes=None, shared_tmp_dir=None, env_extra=None, adaptive=None, min_jobs=None, max_jobs=None, n_workers=1, wait_for_workers=True, worker_timeout=300.0)[source]

Set up a multi-node Dask cluster backed by PBS/Torque.

Uses dask-jobqueue.PBSCluster under the hood, applying MultiNodeConfig defaults and the same error-handling conventions as single-node setup_dask_client().

Parameters:
  • config (MultiNodeConfig | None) – A MultiNodeConfig instance. If None, a default one is constructed from the keyword arguments.

  • workload_type (str | None) – Convenience overrides for any MultiNodeConfig field.

  • workers_per_node (int | None) – Convenience overrides for any MultiNodeConfig field.

  • … – Convenience overrides for any MultiNodeConfig field.

  • n_workers (int) – Number of worker jobs to submit immediately (before any adaptive scaling kicks in).

  • wait_for_workers (bool) – If True (default), block until at least one worker has connected to the scheduler before returning. This prevents the client from appearing to have 0 workers when PBS has not yet started the submitted jobs. Set to False to return immediately and poll client.wait_for_workers() yourself.

  • worker_timeout (float) – Seconds to wait for workers to come online when wait_for_workers is True. Defaults to 300 s (5 minutes). Raise this on queues with long wait times. A TimeoutError is raised (with a diagnostic message) if no workers connect within this window.

  • cores_per_worker (int | None)

  • mem_per_worker_gb (float | None)

  • walltime (str | None)

  • queue (str | None)

  • project (str | None)

  • job_extra_directives (list[str] | None)

  • n_nodes (int | None)

  • shared_tmp_dir (str | Path | None)

  • env_extra (list[str] | None)

  • adaptive (bool | None)

  • min_jobs (int | None)

  • max_jobs (int | None)

Returns:

client — a connected dask.distributed.Client. cluster — the live PBSCluster object. shared_tmp — a SharedTempDir or None.

Return type:

(client, cluster, shared_tmp)

dask_setup.multinode.setup_slurm_cluster(config=None, *, workload_type=None, workers_per_node=None, cores_per_worker=None, mem_per_worker_gb=None, walltime=None, queue=None, project=None, job_extra_directives=None, n_nodes=None, shared_tmp_dir=None, env_extra=None, adaptive=None, min_jobs=None, max_jobs=None, n_workers=1, wait_for_workers=True, worker_timeout=300.0)[source]

Set up a multi-node Dask cluster backed by SLURM.

Uses dask-jobqueue.SLURMCluster under the hood. Mirrors the API of setup_pbs_cluster().

Parameters:
  • config (MultiNodeConfig | None) – A MultiNodeConfig instance.

  • n_workers (int) – Number of worker jobs to submit immediately.

  • wait_for_workers (bool) – If True (default), block until at least one worker has connected to the scheduler before returning. Set to False to return immediately.

  • worker_timeout (float) – Seconds to wait for workers to come online. Defaults to 300 s.

  • workload_type (str | None)

  • workers_per_node (int | None)

  • cores_per_worker (int | None)

  • mem_per_worker_gb (float | None)

  • walltime (str | None)

  • queue (str | None)

  • project (str | None)

  • job_extra_directives (list[str] | None)

  • n_nodes (int | None)

  • shared_tmp_dir (str | Path | None)

  • env_extra (list[str] | None)

  • adaptive (bool | None)

  • min_jobs (int | None)

  • max_jobs (int | None)

Return type:

(client, cluster, shared_tmp)

dask_setup.multinode.generate_pbs_script(cfg, script_path, python_executable='python3', dask_setup_args='')[source]

Generate a PBS job script string that launches a dask_setup-based job.

Parameters:
  • cfg (MultiNodeConfig) – Multi-node configuration.

  • script_path (str) – Path to the user’s Python script to run.

  • python_executable (str) – Python interpreter to use (default python3).

  • dask_setup_args (str) – Additional arguments to pass to the script.

Returns:

Complete #PBS job script content.

Return type:

str

dask_setup.multinode.generate_slurm_script(cfg, script_path, python_executable='python3', dask_setup_args='')[source]

Generate a SLURM job script string.

Parameters:
  • cfg (MultiNodeConfig) – Multi-node configuration.

  • script_path (str) – Path to the user’s Python script to run.

  • python_executable (str) – Python interpreter to use (default python3).

  • dask_setup_args (str) – Additional arguments to pass to the script.

Returns:

Complete #SBATCH job script content.

Return type:

str

Chunking and xarray

dask_setup.xarray.recommend_chunks(ds, client=None, workload_type='auto', target_chunk_mb=(256, 512), safety_factor=0.6, verbose=False, storage_format=None, storage_path=None, chunk_domain=None, **kwargs)[source]

Recommend optimal chunk sizes for an xarray Dataset or DataArray.

Analyzes the dataset characteristics and available Dask worker resources to suggest chunk sizes that balance memory efficiency with good parallelization. Optionally considers storage format for I/O-optimized chunking.

Parameters:
  • ds (xr.Dataset | xr.DataArray) – xarray Dataset or DataArray to analyze

  • client (Client | None) – Active Dask client (to detect worker configuration), or None to use system defaults

  • workload_type (str) – Chunking strategy - “cpu” (compute-heavy), “io” (I/O-heavy), “mixed” (balanced), or “auto” (detect automatically)

  • target_chunk_mb (tuple[float, float]) – (min, max) target chunk size in MiB. Default: (256, 512)

  • safety_factor (float) – Fraction of worker memory to use for chunks. Default: 0.6 (60%)

  • verbose (bool) – If True, print detailed report and return ChunkRecommendation object. If False, return simple dict of chunk sizes.

  • storage_format (str | None) – Optional storage format hint (“zarr”, “netcdf”) for I/O optimization

  • storage_path (str | None) – Optional storage path/URL for format detection and location optimization

  • chunk_domain (str | None) –

    Optional axis family to restrict chunking to.

    "spatial" — only chunk spatial dimensions (lat, lon, x, y, …); all temporal dimensions (time, date, step, …) are loaded fully into a single chunk (equivalent to ds.chunk({"time": -1, ...})).

    "temporal" — only chunk temporal dimensions; all spatial dimensions are loaded fully into a single chunk.

    None (default) — no constraint, the existing behaviour applies and all dimensions are candidates for chunking.

  • **kwargs (Any) – Additional parameters (reserved for future use)

Returns:

A dict mapping dimension names to recommended chunk sizes (locked dimensions appear as -1) when verbose=False; a ChunkRecommendation object with full details when verbose=True.

Raises:
  • XarrayDependencyError – If xarray or numpy are not installed

  • ValueError – If chunk_domain is not one of the accepted values

Return type:

dict[str, int] | ChunkRecommendation

Examples

Basic usage: >>> chunks = recommend_chunks(ds, client) >>> ds_chunked = ds.chunk(chunks)

With detailed analysis: >>> recommendation = recommend_chunks(ds, client, verbose=True) >>> print(recommendation) # Shows detailed report >>> ds_chunked = ds.chunk(recommendation.chunks)

For I/O-heavy workloads: >>> chunks = recommend_chunks(ds, workload_type=”io”)

Chunk only spatial dims, load all time into memory: >>> chunks = recommend_chunks(ds, client, chunk_domain=”spatial”) >>> # chunks == {“lat”: 256, “lon”: 512, “time”: -1} >>> ds_chunked = ds.chunk(chunks)

Chunk only the time axis, load all spatial points into memory: >>> chunks = recommend_chunks(ds, client, chunk_domain=”temporal”) >>> # chunks == {“time”: 120, “lat”: -1, “lon”: -1} >>> ds_chunked = ds.chunk(chunks)

dask_setup.xarray.validate_chunks(ds, client=None, max_chunk_ratio=0.5, min_chunk_mb=10.0)[source]

Validate existing chunk sizes against cluster memory limits.

Compares each dimension’s current chunk size to the per-worker memory available in the cluster, and warns when chunks are dangerously large (OOM risk) or very small (high task-graph overhead).

This function is called automatically by setup_dask_client() when a dataset is passed via the ds= parameter.

Parameters:
  • ds (xr.Dataset or xr.DataArray) – Dataset with existing chunking to validate.

  • client (dask.distributed.Client or None) – Active Dask client used to read per-worker memory limits. When None, falls back to system psutil memory detection.

  • max_chunk_ratio (float) – Maximum acceptable fraction of per-worker memory per chunk. Default 0.5 — chunks larger than 50 % of worker memory trigger a warning.

  • min_chunk_mb (float) – Minimum acceptable chunk size in MiB. Default 10 MiB. Chunks smaller than this trigger a task-overhead warning.

Returns:

Warning messages (empty list if no issues found). The same messages are also emitted via warnings.warn() so they appear in logs.

Return type:

list[str]

Examples

issues = validate_chunks(ds, client)
if issues:
    chunks = recommend_chunks(ds, client)
    ds = ds.chunk(chunks)
class dask_setup.xarray.ChunkRecommendation(chunks, estimated_chunk_mb, total_chunks, warnings_list=None, dataset_info=None)[source]

Bases: object

Container for chunk recommendation results.

Parameters:
dask_setup.rechunk.rechunk_dataset(ds, target_chunks, client, dask_tmp, output_path=None, max_mem='2GB', cleanup_temp=True)[source]

Rechunk an xarray Dataset using rechunker with HPC-friendly temp placement.

Wraps the rechunker library to safely rechunk large datasets to new chunk shapes. Both the intermediate Zarr temp store and the final output Zarr store are written to the Dask spill/temp directory (dask_tmp), which is typically $PBS_JOBFS fast local storage on NCI Gadi.

Parameters:
  • ds (xr.Dataset or xr.DataArray) – Dataset to rechunk. Must be backed by dask arrays (opened lazily).

  • target_chunks (dict[str, int]) – Target chunk sizes, e.g. {"time": 10, "lat": 200, "lon": 200}. Dimensions not listed keep their existing chunk sizes.

  • client (dask.distributed.Client) – Active Dask client used to execute the rechunking task graph.

  • dask_tmp (str or Path) – Dask spill/temp directory — the third element returned by setup_dask_client(). Intermediate and output stores are placed here unless output_path is specified.

  • output_path (str, Path, or None) – Destination Zarr store path for the rechunked dataset. If None, a timestamped subdirectory under dask_tmp is used. This path is not cleaned up automatically; it persists for the job’s lifetime.

  • max_mem (str) – Maximum memory rechunker may use per task, e.g. "1GB", "4GB". Defaults to "2GB". Should be at most 50 % of per-worker memory.

  • cleanup_temp (bool) – If True (default), the rechunker intermediate temp store is deleted after rechunking completes or fails. The output store is never deleted automatically.

Returns:

The rechunked dataset opened lazily from output_path.

Return type:

xr.Dataset or xr.DataArray

Raises:
  • ImportError – If rechunker or zarr are not installed.

  • RuntimeError – If rechunking fails for any reason. Partial output is cleaned up.

Notes

Install optional dependencies with:

pip install rechunker zarr

Examples

from dask_setup import setup_dask_client, recommend_chunks, rechunk_dataset

client, cluster, tmp = setup_dask_client(workload_type="io")
ds = xr.open_zarr("era5.zarr")
chunks = recommend_chunks(ds, client)
ds_rechunked = rechunk_dataset(ds, chunks, client, tmp)
ds_rechunked.to_zarr("era5_rechunked.zarr")

See also

recommend_chunks

Compute optimal target_chunks for a dataset.

validate_chunks

Check whether existing chunks need rechunking.

I/O patterns

dask_setup.io_patterns.recommend_io_chunks(ds, path_or_url=None, client=None, format_hint=None, access_pattern='auto', target_chunk_mb=(128, 512), storage_location='auto', verbose=False)[source]

Recommend I/O-optimized chunks for xarray datasets.

Parameters:
  • ds (xr.Dataset | xr.DataArray) – xarray Dataset or DataArray to optimize

  • path_or_url (str | None) – Optional path/URL to determine storage format and location

  • client (Client | None) – Optional Dask client for cluster info

  • format_hint (str | None) – Optional format override (“zarr”, “netcdf”, etc.)

  • access_pattern (str) – Access pattern hint (“sequential”, “random”, “streaming”, “compute”, “auto”)

  • target_chunk_mb (tuple[float, float]) – Target chunk size range in MiB

  • storage_location (str) – Storage location hint (“local”, “cloud”, “network”, “auto”)

  • verbose (bool) – If True, return full IORecommendation object

Returns:

IORecommendation object if verbose=True, else dict of chunk recommendations

Return type:

IORecommendation | dict[str, int]

dask_setup.io_patterns.detect_storage_format(path_or_url, ds=None)[source]

Detect storage format from path/URL and optional xarray object.

Parameters:
  • path_or_url (str) – File path or URL to analyze

  • ds (Dataset | DataArray | None) – Optional xarray Dataset/DataArray to check for format hints

Returns:

"zarr_v3", "zarr", "netcdf", "kerchunk", or "unknown". "zarr_v3" is returned for zarr-python ≥ 3.0 stores; "kerchunk" is returned for Kerchunk / VirtualiZarr reference stores (checked before zarr, since those stores present a zarr-like interface).

Return type:

Format name

class dask_setup.io_patterns.IORecommendation(format, chunks, compression, storage_options, access_pattern, estimated_throughput_mb_s, warnings, extra=<factory>)[source]

Bases: object

Container for I/O optimization recommendations.

Parameters:
extra: dict[str, Any]

Format-specific extras (e.g. sharding config for Zarr v3).

class dask_setup.io_patterns.ZarrOptimizer(client=None)[source]

Bases: IOOptimizer

Optimizer for Zarr format storage.

Parameters:

client (Client | None)

detect_format(path_or_url, ds=None)[source]

Detect Zarr format from path or xarray backend.

Parameters:
Return type:

bool

optimize_chunks(ds, target_chunk_mb=(128, 512), access_pattern='auto')[source]

Optimize chunks for Zarr format.

Parameters:
Return type:

dict[str, int]

optimize_compression(ds, storage_location='local')[source]

Recommend compression for Zarr.

Parameters:
Return type:

dict[str, Any]

optimize_storage_options(path_or_url, access_pattern='auto')[source]

Optimize storage options for Zarr.

Parameters:
  • path_or_url (str)

  • access_pattern (str)

Return type:

dict[str, Any]

class dask_setup.io_patterns.ZarrV3Optimizer(client=None)[source]

Bases: IOOptimizer

Optimizer for Zarr v3 format storage (zarr-python ≥ 3.0).

Handles the updated zarr.open() interface, sharding via zarr.codecs.ShardingCodec, and the new codec pipeline introduced in zarr>=3.0. When sharding is appropriate (large arrays), the recommended outer/inner chunk shapes are returned in IORecommendation.extra["sharding"].

Parameters:

client (Client | None)

detect_format(path_or_url, ds=None)[source]

Detect Zarr v3 from path pattern or dataset store metadata.

Parameters:
Return type:

bool

optimize_chunks(ds, target_chunk_mb=(128, 512), access_pattern='auto')[source]

Optimize outer chunk sizes for Zarr v3 (with sharding awareness).

Parameters:
Return type:

dict[str, int]

optimize_compression(ds, storage_location='local')[source]

Recommend a zarr v3 codec-pipeline compression configuration.

Parameters:
Return type:

dict[str, Any]

optimize_storage_options(path_or_url, access_pattern='auto')[source]

Optimize storage options for Zarr v3.

Parameters:
  • path_or_url (str)

  • access_pattern (str)

Return type:

dict[str, Any]

class dask_setup.io_patterns.NetCDFOptimizer(client=None)[source]

Bases: IOOptimizer

Optimizer for NetCDF format storage.

Parameters:

client (Client | None)

detect_format(path_or_url, ds=None)[source]

Detect NetCDF format from path or xarray backend.

Parameters:
Return type:

bool

optimize_chunks(ds, target_chunk_mb=(64, 256), access_pattern='auto')[source]

Optimize chunks for NetCDF format.

Parameters:
Return type:

dict[str, int]

optimize_compression(ds, storage_location='local')[source]

Recommend compression for NetCDF.

Parameters:
Return type:

dict[str, Any]

optimize_storage_options(path_or_url, access_pattern='auto')[source]

Optimize storage options for NetCDF.

Parameters:
  • path_or_url (str)

  • access_pattern (str)

Return type:

dict[str, Any]

class dask_setup.io_patterns.KerchunkOptimizer(client=None)[source]

Bases: IOOptimizer

Optimizer for datasets opened via Kerchunk or VirtualiZarr reference stores.

When a dataset is backed by a Kerchunk / VirtualiZarr reference filesystem, chunk boundaries are fixed by the byte ranges of the original files. Rechunking would require a full data copy, defeating the purpose of the virtual store. This optimizer detects the reference-filesystem pattern, returns the existing chunks unchanged, and adds an informational warning.

Parameters:

client (Client | None)

detect_format(path_or_url, ds=None)[source]

Detect Kerchunk/VirtualiZarr reference filesystem datasets.

Parameters:
Return type:

bool

optimize_chunks(ds, target_chunk_mb=(128, 512), access_pattern='auto')[source]

Return the existing chunks — rechunking a Kerchunk dataset is a no-op.

Parameters:
Return type:

dict[str, int]

optimize_compression(ds, storage_location='local')[source]

Compression is fixed in the underlying source files.

Parameters:
Return type:

dict[str, Any]

optimize_storage_options(path_or_url, access_pattern='auto')[source]

Return fsspec options appropriate for a Kerchunk reference store.

Parameters:
  • path_or_url (str)

  • access_pattern (str)

Return type:

dict[str, Any]

dask_setup.parquet.recommend_parquet_chunks(df, client=None, path_or_url=None, target_partition_mb=(128.0, 512.0), compression='auto', storage_location='auto', verbose=False)[source]

Recommend partition sizes for Dask DataFrame Parquet workloads.

Parameters:
  • df (Any) – A Dask or pandas DataFrame (or any object with dtypes, columns, and optionally memory_usage()).

  • client (Client | None) – Optional Dask Client. When provided, per-worker memory limits are used to tighten the upper partition bound.

  • path_or_url (str | None) – Destination path or URL. Used to infer storage location and to recommend appropriate storage_options.

  • target_partition_mb (tuple[float, float]) – (min_mb, max_mb) target partition size range. Defaults to (128, 512).

  • compression (str) – Parquet compression codec. "auto" picks "snappy" for local storage and "zstd" for cloud / network.

  • storage_location (str) – One of "local", "cloud", "network", or "auto".

  • verbose (bool) – If True, return the full ParquetRecommendation and print a report. If False (default), return rows_per_partition only.

Returns:

Return type:

ParquetRecommendation | int

class dask_setup.parquet.ParquetRecommendation(rows_per_partition, compression, storage_options, estimated_partition_mb, warnings, extra=<factory>)[source]

Bases: object

Container for Parquet / Arrow partition recommendations.

Parameters:
rows_per_partition

Recommended number of rows per Dask partition.

Type:

int

compression

Recommended compression codec and options dict, suitable for passing to dask.dataframe.to_parquet(..., compression=...).

Type:

str

storage_options

Recommended storage_options dict for cloud or network targets.

Type:

dict[str, Any]

estimated_partition_mb

Estimated size of each partition in MiB after applying the recommended row count.

Type:

float

warnings

List of advisory messages (empty when all looks healthy).

Type:

list[str]

extra

Format-specific extras, e.g. row_group_size for Parquet v2.

Type:

dict[str, Any]

summary()[source]

Return a one-line human-readable summary.

Return type:

str

Workload inference and tuning

dask_setup.workload.infer_workload_type(ds=None)[source]

Infer the most appropriate workload type from a dataset’s structure.

Examines variable dtypes, dimension names, and array sizes to decide whether the workload is primarily:

  • CPU-bound (→ "cpu"): Many floating-point variables, scientific dimension names (time/lat/lon/lev), large arrays per variable.

  • I/O-bound (→ "io"): Mostly integer / byte / bool variables, many small variables typical of lookup tables or mask datasets.

  • Mixed (→ "mixed"): When the signal is ambiguous or no dataset is provided.

Parameters:

ds (xr.Dataset, xr.DataArray, or None) – Dataset to inspect. When None, returns "mixed" immediately.

Returns:

One of "cpu", "io", or "mixed".

Return type:

str

Notes

The heuristic is intentionally conservative — it returns "mixed" when the evidence is not strong (score difference ≤ 2). Users can always override with an explicit workload_type= argument.

The signal tables are tuned for CF-convention geoscience / climate datasets (the primary NCI Gadi use-case) but are general enough for most scientific array workloads.

Examples

import xarray as xr
from dask_setup import infer_workload_type

ds = xr.open_zarr("era5.zarr")
wt = infer_workload_type(ds)  # likely "cpu"
client, cluster, tmp = setup_dask_client(workload_type=wt)
dask_setup.tune.tune_memory_thresholds(client, strategy='auto', spill_threshold_gib=0.5, tighten_by=0.05, loosen_by=0.05)[source]

Adjust Dask worker memory thresholds based on observed spill behaviour.

Reads current spill metrics from the scheduler, then applies threshold adjustments on every worker:

  • No / low spilling (spill < spill_threshold_gib): tighten memory.target and memory.spill downward by tighten_by so workers retain more head-room before the GC kicks in.

  • Heavy spilling (spill ≥ spill_threshold_gib): loosen the same thresholds upward by loosen_by so workers buffer more data in RAM before reaching the spill threshold, reducing disk write amplification.

Adjustments are clamped to safe ranges: target ∈ [0.40, 0.90], spill ∈ [0.50, 0.95]. If the current value is already at the boundary no change is applied.

Parameters:
  • client (dask.distributed.Client) – A connected Dask client. The scheduler must still be reachable.

  • strategy ({"auto", "tighten", "loosen", "off"}) – "auto" (default) reads spill stats and chooses the direction. "tighten" / "loosen" force the direction regardless. "off" returns immediately without making any changes.

  • spill_threshold_gib (float) – Spill volume (GiB) above which "auto" mode switches to the loosen direction. Default: 0.5 GiB.

  • tighten_by (float) – Fraction to reduce thresholds by when tightening (default 0.05 = 5 pp).

  • loosen_by (float) – Fraction to increase thresholds by when loosening (default 0.05 = 5 pp).

Returns:

Snapshot of what changed and why. Call .summary() for a one-liner suitable for logging.

Return type:

MemoryTuneResult

Examples

from dask_setup import setup_dask_client, tune_memory_thresholds

client, cluster, tmp = setup_dask_client()

# Run a sample batch of tasks first
client.compute(ds.isel(time=slice(0, 10)).mean()).result()

# Now tune based on what was observed
result = tune_memory_thresholds(client)
print(result.summary())
class dask_setup.tune.MemoryTuneResult(strategy, old_target, new_target, old_spill, new_spill, spill_gib_observed, rationale, workers_updated)[source]

Bases: object

Result from a memory threshold tuning pass.

Parameters:
strategy

Effective strategy used: "tighten", "loosen", or "off".

Type:

str

old_target

memory.target value before adjustment (fraction, 0–1).

Type:

float

new_target

memory.target value after adjustment.

Type:

float

old_spill

memory.spill value before adjustment.

Type:

float

new_spill

memory.spill value after adjustment.

Type:

float

spill_gib_observed

Total spill volume observed across all workers at tune time (GiB).

Type:

float

rationale

Human-readable explanation of the decision.

Type:

str

workers_updated

Number of workers whose thresholds were successfully updated. 0 if no change was needed or if the update failed.

Type:

int

summary()[source]

One-line human-readable summary of the tuning result.

Examples

"Memory thresholds adjusted on 4 worker(s): target 75%→70%, spill 85%→80% (low spill...)" "No changes made (strategy='off'; no changes made)"

Return type:

str

dask_setup.callbacks.register_worker_callbacks(client, on_worker_death=None, on_worker_added=None)[source]

Register callbacks for worker lifecycle events on a Dask cluster.

Installs a SchedulerPlugin that calls user-supplied functions when workers join or leave the cluster.

Parameters:
  • client (dask.distributed.Client) – A connected Dask client.

  • on_worker_death (callable(worker_addr: str) | None) – Called when a worker is removed from the scheduler — whether it crashed, was explicitly closed, or was scaled down. worker_addr is the worker’s TCP address string (e.g. "tcp://127.0.0.1:12345").

  • on_worker_added (callable(worker_addr: str) | None) – Called when a new worker connects to the scheduler.

Return type:

None

Notes

  • If neither callback is provided this function is a no-op.

  • The plugin is registered with name="dask_setup_worker_health". Calling this function a second time replaces the existing plugin (idempotent where supported by the installed Dask version).

  • Exceptions raised inside callbacks are caught and logged as warnings so that a buggy callback never crashes the scheduler.

Examples

import threading
from dask_setup import setup_dask_client, register_worker_callbacks

dead: list[str] = []
_lock = threading.Lock()

def on_death(addr: str) -> None:
    with _lock:
        dead.append(addr)

client, cluster, tmp = setup_dask_client()
register_worker_callbacks(client, on_worker_death=on_death)

# ... run tasks ...

if dead:
    print(f"Workers that died during the run: {dead}")

Reporting

dask_setup.reporting.cluster_report(client, start_time=None)[source]

Collect post-run statistics from a connected Dask client.

Queries the distributed scheduler for per-worker memory, spill volume, and task counts. All metric collection is done defensively — any field that cannot be retrieved (e.g. because this Dask version does not expose the metric) is left at its zero/empty default rather than raising.

Parameters:
  • client (dask.distributed.Client) – A connected Dask client. The scheduler must still be reachable; call this function before client.close().

  • start_time (float or None) – Wall-clock start time as returned by time.monotonic(). When provided, ClusterReport.wall_time_seconds is computed as time.monotonic() - start_time. Pass None (the default) to leave wall_time_seconds at 0.0.

Returns:

Snapshot of cluster metrics at the time of the call.

Return type:

ClusterReport

Examples

import time
from dask_setup import setup_dask_client, cluster_report

t0 = time.monotonic()
client, cluster, tmp = setup_dask_client()
# ... do work ...
report = cluster_report(client, start_time=t0)
print(report.summary_line())
class dask_setup.reporting.ClusterReport(wall_time_seconds=0.0, memory_per_worker_gib=<factory>, total_spill_gib=0.0, total_tasks=0)[source]

Bases: object

Summary statistics collected from a Dask cluster at close time.

Parameters:
wall_time_seconds

Elapsed wall-clock time since the cluster was started (or since the start_time passed to cluster_report()). 0.0 if no start time was provided.

Type:

float

memory_per_worker_gib

Managed data held in memory (GiB) per worker address, sampled at report time. Data spilled to disk is excluded; it is counted in total_spill_gib instead. Keys are worker addresses (e.g. "tcp://127.0.0.1:12345").

Type:

dict[str, float]

total_spill_gib

Total bytes written to disk spill storage across all workers (GiB). 0.0 if spill metrics are unavailable in this Dask version.

Type:

float

total_tasks

Number of tasks present in the scheduler graph at report time. This is a snapshot — it is not an all-time cumulative count.

Type:

int

property wall_time_str: str

Human-readable elapsed wall time (e.g. "2h 5m 30s").

property peak_memory_gib: float

Maximum memory across all workers (GiB), or 0.0 if unknown.

property total_memory_gib: float

Sum of in-memory managed data across all workers (GiB).

summary_line()[source]

One-line summary suitable for logging or printing.

Example output:

wall=1m 23s | workers=4 | mem=3.21 GiB/max | spill=0.00 GiB | tasks=1842
Return type:

str

to_dict()[source]

Return a JSON-serialisable dictionary of all report fields.

Returns:

All fields including derived properties (wall_time_str, peak_memory_gib, total_memory_gib).

Return type:

dict

Benchmarking

dask_setup.benchmark.benchmark_config(configs, ds, operation='mean', *, repeats=1, warmup=False, fallback_on_detection_failure=True, verbose=False)[source]

A/B-test multiple configurations against the same xarray operation.

Each configuration is run in a fresh cluster so that memory state, worker topology, and spill behaviour are fully independent between runs.

Parameters:
  • configs (dict[str, DaskSetupConfig] | list[DaskSetupConfig] | DaskSetupConfig) –

    Configurations to compare. May be:

    • A dict[name, DaskSetupConfig] — explicit names.

    • A list[DaskSetupConfig] — names are "config_0", etc.

    • A single DaskSetupConfig — returns a one-element list.

  • ds (Any) – xarray Dataset or DataArray to benchmark against. Should not already be chunked — benchmark_config() will chunk it according to each config’s recommended chunks (or leave it as-is if no chunking is suggested).

  • operation (Callable[[Any], Any] | str) – Operation to time. Either a callable fn(ds) -> lazy_result or one of the string aliases: "mean", "sum", "std", "max", "min", "var", "rechunk".

  • repeats (int) – Number of timed repetitions per configuration. Mean and std-dev of wall time are stored in each BenchmarkResult.

  • warmup (bool) – If True, run one un-timed warmup pass before the timed repeats. Useful for eliminating cold-start overhead (e.g. JIT compilation).

  • fallback_on_detection_failure (bool) – Passed to setup_dask_client().

  • verbose (bool) – If True, print a summary line after each configuration completes.

Returns:

One entry per configuration, in the same order as configs.

Return type:

list[BenchmarkResult]

Examples

from dask_setup import DaskSetupConfig
from dask_setup.benchmark import benchmark_config

results = benchmark_config(
    {
        "io":  DaskSetupConfig(workload_type="io"),
        "cpu": DaskSetupConfig(workload_type="cpu"),
    },
    my_ds,
    operation="mean",
    repeats=3,
)
for r in sorted(results, key=lambda r: r.wall_time_seconds):
    print(r.summary_line())
dask_setup.benchmark.scaling_analysis(ds, operation='mean', worker_counts=(1, 2, 4, 8), *, base_config=None, repeats=1, warmup=False, fallback_on_detection_failure=True, mode='local', plot=False, verbose=False)[source]

Measure parallel scaling across different worker counts.

For each entry in worker_counts, a fresh local cluster is created with max_workers set to that count and the operation is timed. The resulting ScalingResult includes speedup and efficiency relative to the single-worker baseline.

Parameters:
  • ds (Any) – xarray Dataset or DataArray to benchmark.

  • operation (Callable[[Any], Any] | str) – Callable or string alias (see benchmark_config()).

  • worker_counts (Sequence[int]) – Sequence of worker counts to sweep. The first entry is used as the baseline for speedup calculations.

  • base_config (DaskSetupConfig | None) – Base DaskSetupConfig to use for each run. max_workers is overridden per run. Defaults to DaskSetupConfig() (library defaults).

  • repeats (int) – Timed repetitions per worker count.

  • warmup (bool) – Run one un-timed warmup pass before timing.

  • fallback_on_detection_failure (bool) – Passed to setup_dask_client().

  • mode (str) – Cluster mode passed to setup_dask_client(). Defaults to "local" so that the sweep always uses a LocalCluster on the current node — this prevents auto-detection from dispatching to PBS/SLURM and submitting child scheduler jobs, which would time out and produce NaN results. Pass "auto" only if you specifically want multi-node dispatch for each sweep step.

  • plot (bool) – If True, call ScalingResult.plot() and display the figure (requires matplotlib).

  • verbose (bool) – Print a summary line after each worker count.

Returns:

Scaling metrics for all worker counts.

Return type:

ScalingResult

dask_setup.benchmark.chunk_impact(ds, client, operation='mean', chunk_sizes=None, *, auto_chunks=True, repeats=1, warmup=False, plot=False, verbose=False)[source]

Sweep chunk sizes on a fixed cluster to find the throughput sweet spot.

Unlike benchmark_config() and scaling_analysis(), this function does not create or destroy clusters — it uses the client you provide. The dataset is rechunked before each measurement.

Parameters:
  • ds (Any) – xarray Dataset or DataArray. Must have known dimension sizes.

  • client (Client) – Connected Dask client. The cluster remains running throughout.

  • operation (Callable[[Any], Any] | str) – Callable or string alias (see benchmark_config()).

  • chunk_sizes (list[dict[str, int]] | None) – List of chunk-size dicts to try (e.g. [{"time": 10, "lat": 50}, {"time": 20, "lat": 100}]). If None and auto_chunks=True, a geometric series of five sizes is generated automatically.

  • auto_chunks (bool) – Generate chunk sizes automatically when chunk_sizes is None.

  • repeats (int) – Timed repetitions per chunk size.

  • warmup (bool) – Run one un-timed warmup pass per chunk size.

  • plot (bool) – Display the wall-time vs chunk-size plot (requires matplotlib).

  • verbose (bool) – Print a summary line after each chunk size is tested.

Returns:

Timing results for each chunk spec, plus the recommended spec.

Return type:

ChunkImpactResult

dask_setup.benchmark.run_synthetic_benchmark(profile_name='development', operation='mean', ds_size='small', repeats=1, fallback_on_detection_failure=True, verbose=False)[source]

Run a synthetic benchmark using dask.array (no xarray required).

Creates a random floating-point array of the given size, sets up a Dask cluster from the named profile, and times the requested operation.

This function is the backend for the dask-setup benchmark CLI subcommand.

Parameters:
  • profile_name (str) – Name of a builtin or user profile to use. "development" is the safe default for quick tests.

  • operation (str) – One of "mean", "sum", "std", "max", "min".

  • ds_size (str) – One of "tiny", "small", "medium", "large".

  • repeats (int) – Timed repetitions.

  • fallback_on_detection_failure (bool) – Passed to setup_dask_client().

  • verbose (bool) – Print progress messages.

Return type:

SyntheticBenchmarkResult

class dask_setup.benchmark.BenchmarkResult(name, wall_time_seconds, wall_time_std=0.0, peak_memory_gib=0.0, spill_gib=0.0, n_tasks=0, n_workers=0, tasks_per_second=0.0, errors=<factory>, extra=<factory>)[source]

Bases: object

Metrics for a single benchmarked configuration / parameter setting.

Parameters:
name

Label for this result (config name, worker count, chunk spec, etc.).

Type:

str

wall_time_seconds

Mean elapsed time for the operation across all repeats.

Type:

float

wall_time_std

Standard deviation of per-repeat wall times (0.0 for a single repeat).

Type:

float

peak_memory_gib

Highest total worker process memory (RSS summed across workers) observed during the timed runs, sampled every 0.2 s. If in-flight sampling was unavailable this falls back to a post-run reading and an explanatory note is appended to errors.

Type:

float

spill_gib

Total data written to disk spill storage during the run.

Type:

float

n_tasks

Number of dask-graph tasks the operation required.

Type:

int

n_workers

Number of workers active during this run.

Type:

int

tasks_per_second

Derived: n_tasks / wall_time_seconds (0.0 if either is unknown).

Type:

float

errors

Any non-fatal error messages collected during the run.

Type:

list[str]

extra

Additional metadata attached by the calling function (e.g. {"chunk_size_mb": 128} from chunk_impact()).

Type:

dict[str, Any]

summary_line()[source]

One-line human-readable summary.

Return type:

str

to_dict()[source]

Return a JSON-serialisable dictionary of all fields.

Return type:

dict[str, Any]

class dask_setup.benchmark.ScalingResult(results, worker_counts, speedups, efficiencies)[source]

Bases: object

Results of a worker-count scaling sweep.

Parameters:
results

One entry per worker count, in the order they were benchmarked.

Type:

list[BenchmarkResult]

worker_counts

The worker counts that were swept.

Type:

list[int]

speedups

wall_times[0] / wall_times[i] relative to the single-worker run.

Type:

list[float]

efficiencies

speedups[i] / worker_counts[i] — ideal scaling gives 1.0.

Type:

list[float]

property wall_times: list[float]

Wall times in the same order as worker_counts.

best()[source]

Return the result with the shortest wall time.

Return type:

BenchmarkResult

summary()[source]

Multi-line summary table.

Return type:

str

to_dataframe()[source]

Return results as a pandas.DataFrame.

Raises ImportError if pandas is not installed.

Return type:

Any

plot(figsize=(10, 4), title='Scaling Analysis')[source]

Plot speedup and efficiency curves.

Returns the matplotlib Figure, or None if matplotlib is not installed.

Parameters:
Return type:

Any

class dask_setup.benchmark.ChunkImpactResult(results, chunk_specs, recommended_chunks=None)[source]

Bases: object

Results of a chunk-size sweep on a fixed cluster.

Parameters:
results

One entry per chunk specification, in the order they were tried.

Type:

list[BenchmarkResult]

chunk_specs

The chunk specs that were swept. Alias for chunk_sizes.

Type:

list[dict[str, int]]

recommended_chunks

The chunk spec with the best (lowest) wall time.

Type:

dict[str, int]

property chunk_sizes: list[dict[str, int]]

Alias for chunk_specs for backward compatibility.

optimal()[source]

Return the result with the best (lowest) wall time.

Return type:

BenchmarkResult

to_dataframe()[source]

Return results as a pandas.DataFrame.

Raises ImportError if pandas is not installed.

Return type:

Any

plot(dim=None, figsize=(8, 4), title='Chunk Size Impact')[source]

Plot wall time vs chunk size for one dimension.

Parameters:
  • dim (str | None) – Dimension name to use as the x-axis. If None, uses the first dimension found in chunk_sizes.

  • Figure (Returns the matplotlib)

  • not (or None if matplotlib is)

  • found. (installed or no suitable dimension is)

  • figsize (tuple[float, float])

  • title (str)

Return type:

Any

Environment and logging

dask_setup.environment.is_jupyter()[source]

Return True if running inside a Jupyter kernel.

Covers JupyterLab, classic Jupyter Notebook, VSCode notebooks, Google Colab, and any other environment that uses a ZMQ kernel.

The result is cached after the first call so it is safe to call repeatedly inside hot paths.

Return type:

bool

Examples

from dask_setup.environment import is_jupyter

if is_jupyter():
    print("Running in Jupyter")
dask_setup.environment.get_environment_type()[source]

Return a string describing the detected runtime environment.

Returns:

  • str – One of "jupyter", "ipython", or "script".

  • The result is cached after the first call.

Return type:

str

dask_setup.logging.configure_logging(level='INFO', format_style='structured', use_color=None)[source]

Configure logging for dask_setup package.

Parameters:
  • level (str | int) – Logging level (string or logging constant)

  • format_style (str) – “structured” for human-readable, “json” for structured data

  • use_color (bool | None) – Whether to use color output (None = auto-detect)

Return type:

None

dask_setup.logging.get_logger(module_name)[source]

Convenience function to get a logger for a module.

Parameters:

module_name (str) – Name of the module (e.g., ‘client’, ‘resources’)

Returns:

DaskSetupLoggerAdapter instance

Return type:

DaskSetupLoggerAdapter

Errors

Custom exceptions for the dask_setup package.

exception dask_setup.exceptions.DaskSetupError[source]

Bases: Exception

Base exception for all dask_setup errors.

exception dask_setup.exceptions.InsufficientResourcesError(required_mem, available_mem, suggested_actions=None)[source]

Bases: DaskSetupError

Raised when system resources are insufficient for the requested configuration.

Parameters:
Return type:

None

exception dask_setup.exceptions.InvalidConfigurationError[source]

Bases: DaskSetupError

Raised when configuration parameters are invalid.

exception dask_setup.exceptions.ResourceDetectionError[source]

Bases: DaskSetupError

Raised when resource detection fails.

class dask_setup.error_handling.ErrorContext(platform_info=<factory>, python_version=<factory>, total_memory_gb=<factory>, cpu_count=<factory>, hpc_environment=None, pbs_jobfs=<factory>, slurm_job_id=<factory>, available_dependencies=<factory>)[source]

Bases: object

Container for error context information.

Parameters:
  • platform_info (str)

  • python_version (str)

  • total_memory_gb (float)

  • cpu_count (int)

  • hpc_environment (str | None)

  • pbs_jobfs (str | None)

  • slurm_job_id (str | None)

  • available_dependencies (dict[str, str])

get_environment_summary()[source]

Get a summary of the current environment.

Return type:

str

class dask_setup.error_handling.EnhancedDaskSetupError(message, suggestions=None, context=None, documentation_url=None, error_code=None)[source]

Bases: DaskSetupError

Base class for enhanced dask_setup errors with rich context.

Parameters:
get_diagnostic_info()[source]

Get diagnostic information for error reporting.

Return type:

str

class dask_setup.error_handling.ConfigurationValidationError(field_errors, invalid_config=None, context=None)[source]

Bases: EnhancedDaskSetupError

Enhanced configuration validation error with specific field guidance.

Parameters:
class dask_setup.error_handling.ResourceConstraintError(resource_type, required, available, units='GB', context=None)[source]

Bases: EnhancedDaskSetupError

Enhanced resource constraint error with HPC-aware suggestions.

Parameters:
class dask_setup.error_handling.DependencyError(missing_package, feature, context=None, alternative_packages=None)[source]

Bases: EnhancedDaskSetupError

Enhanced dependency error with installation guidance.

Parameters:
class dask_setup.error_handling.StorageConfigurationError(storage_issue, storage_path=None, detected_format=None, context=None)[source]

Bases: EnhancedDaskSetupError

Enhanced storage configuration error with format-specific guidance.

Parameters:
  • storage_issue (str)

  • storage_path (str | None)

  • detected_format (str | None)

  • context (ErrorContext | None)

class dask_setup.error_handling.ClusterSetupError(setup_issue, cluster_config=None, context=None)[source]

Bases: EnhancedDaskSetupError

Enhanced cluster setup error with diagnostic information.

Unlike other EnhancedDaskSetupError subclasses, ClusterSetupError automatically appends an environment summary (platform, Python version, memory, CPUs, and detected dependencies) to the formatted error message. This makes it easier to diagnose failures without separately calling get_diagnostic_info().

Parameters:
dask_setup.error_handling.create_user_friendly_error(error_type, message, **kwargs)[source]

Factory function to create user-friendly errors.

Parameters:
  • error_type (str)

  • message (str)

Return type:

EnhancedDaskSetupError

dask_setup.error_handling.format_exception_chain(exc)[source]

Format an exception chain with context for debugging.

Parameters:

exc (Exception)

Return type:

str