Native HEC-RAS on Slurm with Apptainer¶
RasApptainer runs a HEC-RAS 5.0.7, 6.1, 6.5, 6.6 or 7.x native-Linux unsteady plan on any Slurm cluster
that has Apptainer, starting from the artifacts that Windows preprocessing produced
(<project>.p##.tmp.hdf, <project>.b##, <project>.x##, plus <project>.c## for releases
before 6.3.1 such as 5.0.7 and 6.1; see
RasPreprocess). Those artifacts
can come from a Windows workstation or from a
Wine prepare job on the same cluster, which mirrors
RasDocker.preprocess_plan followed by RasDocker.compute_plan. It is cluster-agnostic:
everything site-specific lives in a JSON site profile.
What a receipt means
The receipt is computational and transfer evidence (exit codes, hashes, timings). It is not a statement that the results are acceptable engineering output. Review results as usual.
Choose this API for a prepared native unsteady plan. For portable execution requests, use RasSlurm; the backend comparison explains the different input contracts.
Concepts¶
| Piece | What it does |
|---|---|
| Site profile | JSON file: SSH target, shared staging root, node scratch, SIF path and SHA-256, Slurm account/partition/qos, cores, memory, time |
| Image | One immutable OCI image pulled once by an administrator into a shared SIF. Every job re-verifies the SIF SHA-256 before use |
| Job folder | Rendered locally: inputs/ (tmp.hdf, .b, .x, the .c for pre-6.3.1 releases, SHA256SUMS), engine.sh, job.sh, job.json |
| Prepare job (optional) | Rendered locally from a complete project: inputs/ (the project tree), job.sh, job.json. Runs Windows preprocessing under Wine in a separate executor SIF and returns the solver inputs |
| Job run | Verifies image and inputs, copies to node-local scratch, runs the solver in Apptainer, hash-verifies the copy-back, writes receipt.json |
| Transport | Pluggable object with run, put_tree, get_tree. SshApptainerTransport uses OpenSSH/scp; tests use a fake |
Unknown fields are rejected (including other tools' fields such as memory_per_task or
max_concurrent; use slurm_memory), mutable image tags are rejected, and the SIF SHA-256 is mandatory at real
submission time (the all-zero example placeholder is allowed only for local rendering/dry-run).
Site profile¶
Start with a site-specific JSON profile. The example profile shows the schema; replace its SSH target, storage paths, account, partition, and node selection for your cluster. The credentials and SIF hashes are placeholders. Key fields:
| Field | Meaning |
|---|---|
host, ssh_user, ssh_port, identity_file, known_hosts_file |
SSH target. Host keys are always checked (StrictHostKeyChecking=yes); pin the controller key in known_hosts_file |
scratch_root |
Shared filesystem root visible to login and compute nodes; job folders are staged below it |
node_scratch_root |
Node-local scratch (default /scratch); the job works in <root>/$SLURM_JOB_ID/ras |
image, apptainer_image_sha256, container_identity |
Shared SIF path, its SHA-256, and the immutable identity (sif:sha256:<hex> or registry/repo@sha256:<hex>) |
oci_source |
OCI reference used for the one-time pull; it may be tag- or digest-pinned (docker://repo@sha256:...) |
hecras_dir, ld_library_path |
Solver directory inside the image and its library path. The defaults are derived from the canonical image Dockerfile and registry metadata, not a live API qualification. For the bin_ras layout an omitted hecras_dir defaults to /opt/hecras-runtime/engine/bin_ras (where the native 5.0.7 image puts HEC's bin_ras folder) and an omitted ld_library_path to hecras_dir alone (HEC colocates the 5.0.7 libraries with the binary). |
native_layout |
Optional library layout: canonical (6.1+/7.x RasUnsteady with a libs/ tree) or bin_ras (5.0.7 rasUnsteady64 with colocated libraries). Omit it to derive the layout from ras_version; a layout that does not match the release is rejected. |
solver_arguments |
Optional solver argument convention, independent of the layout: cfile (<engine> <project>.c## b##) or tmphdf (RasUnsteady <project>.p##.tmp.hdf x##). Omit it to derive it from ras_version: releases before 6.3.1 (5.0.7, 6.1; 6.2 and 6.3 are treated the same) use cfile, 6.3.1 and later tmphdf. bin_ras accepts only cfile. An explicit value is recorded in the request. See Solver argument conventions. |
stack_unlimited, omp_stacksize, kmp_stacksize |
When stack_unlimited=true, the job checks both host and container stack limits; it fails with STACK_LIMIT_FAILED if either cannot be raised. OMP_STACKSIZE/KMP_STACKSIZE default to 2G; OMP_NUM_THREADS/MKL_NUM_THREADS follow num_cores. |
num_cores, slurm_memory, time_limit, account, partition, qos, nodelist |
Slurm resources (all but num_cores optional) |
geom_preprocess |
Enable only for an image that ships RasGeomPreprocess. When false (the default), 6.x/7.x inputs must already contain non-empty /Geometry/GeomPreprocess. Rejected for bin_ras: the 5.0.7 Linux distribution has no RasGeomPreprocess. Also rejected for cfile releases (for example 6.1), which consume the Windows-computed .c## geometry. |
ras_version |
HEC-RAS release of the native engine: 5.0.7, 6.1, 6.5, 6.6, or 7.x (for example 7.0.1). HEC published no Linux engine for 5.0–5.0.6, 6.0, or 6.2–6.4.1; those are rejected (run them entirely under Wine with RasSlurm). 4.x is not supported in containers. It also selects the default Windows Ras.exe for prepare jobs |
prepare_image, prepare_image_sha256 |
Optional shared Wine executor SIF and its SHA-256, required only by render_prepare_job. Set both or neither |
prepare_ras_executable, prepare_python_executable, prepare_timeout_seconds |
Windows Ras.exe inside the executor (default C:\Program Files (x86)\HEC\HEC-RAS\<ras_version>\Ras.exe), the image's Linux Python (default python), and the preprocessing timeout (default 3600 s) |
Canonical image and pulling it once¶
The default image is rascommander/hec-ras-linux-unsteady_6.6:v1
(Docker Hub, linux/amd64).
The container catalog lists its registry digest and Docker qualification.
Its configured layout (/opt/hecras-runtime/engine, library directories beneath it, and no
RasGeomPreprocess) is derived from its Dockerfile and registry metadata. The same layout is used
for 6.5 and 7.0.1 (rascommander/hec-ras-linux-unsteady_<version>:v1); set ras_version and
oci_source together.
Production evidence
CLB has run RasApptainer.render_job, submit, and collect live on its Slurm cluster
with a site-built HEC-RAS 6.6 native SIF (ras-hecras.sif, which ships RasGeomPreprocess).
Those jobs collected successfully with verified copy-back, populated unsteady results, and the
planned final simulation time. On October 13–14, 2026 a Wine prepare job followed by a native
solve ran end to end through this API for HEC-RAS 5.0.7, 6.1, 6.5, 6.6, 7.0, and 7.0.1 on the
Muncie 2D plan; see the
qualification table. Water surfaces matched the
Windows engine under Wine to within 0.012 ft at any time step (maximum-WSE map within
0.0005 ft). Qualify the image you pull with one representative plan before a campaign.
An administrator pulls it once, on a node, to shared storage, then records the digest:
apptainer pull /shared/images/rascommander-hec-ras-linux-unsteady_6.6-v1.sif docker://rascommander/hec-ras-linux-unsteady_6.6:v1
sha256sum /shared/images/rascommander-hec-ras-linux-unsteady_6.6-v1.sif
RasApptainer.pull_command(profile) prints this command for a profile. To reuse a local or cached SIF (or fill a shared cache once), use
RasContainerImages.resolve_sif() or ensure_sif_command(); see
Use a local or cached image.
Put the digest in
apptainer_image_sha256 and container_identity (sif:sha256:<digest>). Jobs refuse to run on a
mismatch. Do not pull per job.
HEC-RAS 5.0.7 (bin_ras layout)¶
HEC's 5.0.7 Linux distribution (HEC-RAS_507_linux.zip) ships bin_ras/rasUnsteady64 with its
libraries alongside it, not the 6.x RasUnsteady plus libs/ tree. With ras_version: "5.0.7"
the profile derives native_layout: "bin_ras", and RasApptainer mirrors
RasCmdr.compute_plan_linux for that layout:
- Windows 5.0.7 preprocessing must also have produced the computed-geometry file
<project>.c<geom>(the geometry number comes from the plan'sGeom File=g##).render_jobrequires it to be present and non-empty and stages it byte-for-byte; it is binary and is never line-ending converted (the.b/.xtext files still are). - The engine runs
"$HECRAS/rasUnsteady64" <project>.c<geom> b<plan>with cwd/job(cwd-relative basenames, as in HEC's own run script) andLD_LIBRARY_PATHset tohecras_dirby default. It fails with exit code 6 and a clear message if$HECRAS/rasUnsteady64is not executable. The solver writes results into the tmp HDF, which is promoted to<project>.p##.hdfafter the completion banner and fatal-marker checks, as for 6.x. - The compiled-HDF core attributes are not rewritten (5.0.7 stores
1D Cores/2D Coresscalars thatcompute_plan_linuxalso leaves alone);OMP_NUM_THREADS/MKL_NUM_THREADSfollownum_cores. render_jobrefuses a 5.x tmp HDF with a 6.x/7.x profile, and a 6.x/7.x tmp HDF with abin_rasprofile, based on the rootFile Versionattribute.- The rendered request records
native_layout: "bin_ras"andctoken; canonical requests are unchanged.
The image must provide rasUnsteady64 and its libraries in hecras_dir
(default /opt/hecras-runtime/engine), including a libgfortran.so.3 link to HEC's
libgfortran.so (the binary needs that soname); the
native image recipe
builds it from HEC-RAS_507_linux.zip. No published 5.0.7 native image exists yet: set image,
apptainer_image_sha256, container_identity and oci_source for the SIF you build. Collection
validation is the same as for 6.x, since the 5.0.7 result HDF has the same
Results/Unsteady/.../Time Date Stamp structure. On October 14, 2026 a 5.0.7 prepare job (Wine)
and native
solve of Muncie plan 03 ran on the CLB cluster: 74 s on 2 cores, water surfaces within 0.012 ft of
the Windows engine under Wine.
Solver argument conventions by release¶
The library layout and the solver's argument convention are independent. Verified against HEC's official Linux distributions:
| Release | Library layout | Solver arguments (cwd = project) | Needs .c## |
|---|---|---|---|
5.0.7 (HEC-RAS_507_linux.zip) |
bin_ras |
rasUnsteady64 <project>.c<geom> b<plan> (cfile) |
yes |
6.1 (HEC-RAS_610_Linux.zip) |
canonical (RasUnsteady + libs/, libs/mkl, libs/rhel_8) |
RasUnsteady <project>.c<geom> b<plan> (cfile) |
yes |
| 6.5, 6.6, 7.x | canonical | RasUnsteady <project>.p<plan>.tmp.hdf x<geom> (tmphdf) |
no |
HEC's 6.1 solver exits with code 29 when given the 6.3.1+ tmp.hdf x## arguments. HEC published
no Linux engine for 6.0 or 6.2–6.4.1. For a cfile profile with the
canonical layout (ras_version: "6.1"), render_job stages the non-empty .c## file byte-for-byte,
leaves the compiled-HDF core attributes untouched (OMP_NUM_THREADS/MKL_NUM_THREADS still follow
num_cores), runs "$HECRAS/RasUnsteady" "$PROJECT.$CTOKEN" "b$PLAN", and records
native_layout: "canonical", solver_arguments: "cfile" and ctoken in the request. render_job
also refuses a tmp HDF whose root File Version release uses a different convention than the
profile (for example a 6.1 file with a 6.6 profile). Requests, engine scripts and job scripts for
6.3.1+ profiles are byte-identical to earlier versions. The 6.1 path ran live on October 14, 2026
(Muncie plan 03, 40 s on 2 cores, water surfaces within 0.0085 ft of the Windows engine under Wine).
RasCmdr.compute_plan_linux (and the RasDocker native worker that calls it) chooses the same
convention from the tmp HDF File Version, falling back to the install layout (bin_ras →
cfile, otherwise tmphdf) when the attribute is absent. It finds HEC's 6.1 libs/ tree two
levels above Ras_v61/Release, and when libs/rhel_8 exists it omits libs/centos_7, as HEC's
run_unsteady.sh does.
Solver-ready inputs and the pre-submit check¶
render_job first runs ras_commander.RasApptainer.check_solver_ready on the local, completed tmp.hdf, read-only with HDF5 locking off.
It raises InputCheckError (exported from ras_commander) listing every problem, before anything is staged, when:
Event Conditionsdoes not reportCompleted Successfully=True(including a missing attribute),- point or gridded precipitation lacks a non-empty two-dimensional
Event Conditions/Meteorology/Precipitation/Valuesdataset, - gridded precipitation is present but
Event Conditions/Meteorology/Precipitation/2D Flow Areas/<area>/lacksCell/Face Indexes/Info/Weights(solver: "2D Flow Areas folder not found"), or - any floating 2D property table contains NaN (including face minimum elevation, face area/elevation,
cell volume/elevation, and cell surface area).
Cells Minimum Elevationlegitimately holds NaN and is ignored, or geom_preprocess=falseand/Geometry/GeomPreprocessis absent or empty.
These 2D checks are applied only to 2D areas that exist, so completed 1D/storage-area-only plans
are supported. For a tmp HDF from a release before 6.3.1 (root File Version HEC-RAS 5.* or
HEC-RAS 6.1–6.3) the /Geometry/GeomPreprocess and 2D property-table NaN checks are skipped,
because those releases read their computed geometry, including the 2D property tables, from the
.c## file; render_job requires that non-empty .c## file instead. Completed Successfully is
skipped only for 5.x, which never writes it; it stays required for 6.1. The 6.1 relaxation follows
from the cfile invocation and has not yet been checked against a live 6.1 Windows output.
Never run it on a file HEC-RAS is still writing. Pass check_inputs=False to skip.
Gridded DSS dependencies¶
For a materialized precipitation group with Mode=Gridded and Source=DSS,
render_job treats DSS Filename and DSS Pathname as an input dependency.
The filename must be a non-empty relative path from the temporary HDF, resolve
to an existing regular .dss file within the source project's parent directory,
and fit a collision-resistant staged filename in the HDF attribute's fixed-width
capacity. Absolute paths, symlinks, missing pathnames, and paths that escape
that directory fail before staging.
The renderer copies the DSS into inputs/ under a digest-derived filename and
rewrites only the staged temporary HDF to that relative filename. It reads
the attribute back and confirms that Mode, Source, DSS Pathname, and
Ratio did not change. The source HDF and DSS remain unchanged. Source and
staged DSS hashes, the source reference, staged name, and DSS pathname are
recorded in the request and input manifest. check_solver_ready runs again
after this staged-HDF change.
Completed Windows preprocessing¶
RasPreprocess does not treat the presence of gridded rainfall mapping datasets
as proof that native preprocessing has finished. It waits for this launch's
exact RasProcess.exe CompletePreProcess writer to be observed and then exit,
followed by two seconds of unchanged file-stat state. It does not open the HDF
while the owned engine lifecycle is active. If that exact writer is never
observed, the bounded run reports a timeout instead of authorizing an early
stop. After owned processes have ended and the file is quiescent, it performs
the structural and full solver-ready checks. Missing geometry preprocessing,
precipitation mappings, or non-finite property-table values remain terminal
input failures; they are never repaired by the staging path.
Point precipitation repair¶
A temporary HDF without completed point precipitation is produced by HEC-RAS
itself, not by the early stop. HEC-RAS's event-condition writer,
RasProcess.exe CompletePreProcess, intermittently writes no point
precipitation for identical inputs. It reports only:
Processing Precipitation data...
No valid precipitation time-series data found for this time-window: (...) -> (...)
Error processing Precipitation data, exiting...
Ras.exe then exits 0 before the unsteady computation, so the run ends with
signal_source="natural_completion", Completed Successfully=False and no
Precipitation/Values. With HEC-RAS 6.6 under Wine 11.0, 8 of 47
CompletePreProcess processes on one fixed temporary HDF failed this way
(17%, two nodes); HEC-RAS 6.4.1 behaved the same. The gauge DSS series read
without error in every case. Native Windows has not been checked.
When a point-precipitation plan fails Event Conditions validation and the
temporary HDF, .b## and .x## are all present, preprocess_plan() re-runs
only RasProcess.exe CompletePreProcess <project>.rasmap <plan>.tmp.hdf on
the same temporary HDF and validates it again after each attempt. Each
attempt runs from the project folder, because gauge DSS paths in the HDF are
relative (.\Hydrology\<file>.dss); from any other folder every gauge reports
that its file does not exist. On a 794,234-cell mesh one attempt took about a
minute, against about 20 minutes for full preprocessing, and the repaired
file matched a first-try success.
precipitation_repair_attempts (default 6; 0 disables) bounds the attempts,
and precipitation_repair_timeout (default max_wait) bounds each one.
PreprocessResult.precipitation_repair_attempts records every attempt's exit
code, elapsed time, whether "No valid precipitation" appeared and the
remaining validation errors. A repaired result reports
signal_source="completepreprocess_repair". Gridded precipitation is not
repaired this way.
Usage¶
from pathlib import Path
from ras_commander import RasApptainer, SshApptainerTransport
profile = RasApptainer.load_profile("my-site.json")
# 1. Render locally. No network. The source project is not modified.
job = RasApptainer.render_job(
project_folder=r"C:\models\UPGU1", project_name="UPGU1", plan_number="01",
profile=profile, job_directory=r"C:\jobs\upgu1_p01",
)
# 2. Dry run (default): returns the handle without contacting the cluster.
RasApptainer.submit(job, dry_run=True)
# 3. Real submission.
transport = SshApptainerTransport(profile)
job = RasApptainer.submit(job, transport=transport, profile=profile, dry_run=False)
# 4. Later (even from a new process): reload the handle, poll, collect.
job = RasApptainer.load_job(r"C:\jobs\upgu1_p01")
print(RasApptainer.status(job, transport, profile))
result = RasApptainer.collect(job, transport, profile)
print(result.success, result.problems)
Status and collection¶
status uses sacct (falling back to squeue) and reports UNKNOWN with a reason when either
accounting command is unavailable. cancel(job, transport, profile) calls scancel only for that
job's recorded allocation.
collect requires a terminal allocation, downloads out/<jobid> into a new local directory,
validates the receipt schema, checks that the receipt is bound to this request and Slurm job, and
re-hashes every output. success is true only when the scheduler state is COMPLETED, the receipt
says succeeded, all hashes match, /Results/Unsteady is populated, and the final simulation time
matches the planned end time. Failed jobs are collected too so their logs and receipt are retained.
Safety properties¶
- The request SHA-256 binds the project, plan, staged input manifest, input hashes, normalized
job.shhash,engine.shhash, expected simulation end, and profile. Submission verifies staged local and remote inputs against that request rather than trusting a mutable manifest; collection verifies receipt image identity and input hashes against it. - Every render uses a unique remote attempt directory. An interrupted pre-submission upload can be
reconciled by cleaning only that exact attempt; ambiguous
sbatchoutcomes remain non-retryable. - The submission intent is persisted before
sbatch. Ifsbatchfails ambiguously the job stays inSUBMITTINGand is never resubmitted automatically; reconcile on the cluster and render a new job. - Only node-scratch copies are modified (
.bfiles are staged with LF line endings; the source hashes and the staged hashes are both recorded injob.json). IfWrite DSS File = Tis followed by a Windows-absolute DSS path, the staged.bcopy rewrites that path to the relative<project>.dss; the original is untouched and the request and receipt record the rewrite. - Copy-back goes to
out/<jobid>.partial, is hash-verified, then renamed. Scratch is kept when the job fails and removed only after a verified success. - The staged HDF core attributes (
1D Cores,2D Cores (per mesh)) are rewritten tonum_cores; the allocation and OpenMP/MKL thread counts use the same value.
Wine preparation on the cluster¶
Instead of preprocessing on a Windows workstation, run Windows HEC-RAS preprocessing under Wine on the cluster. Add the Wine executor SIF to the profile:
"prepare_image": "/shared/images/hec-ras-wine-executor_6.6.sif",
"prepare_image_sha256": "<sha256sum of that SIF>"
For HEC-RAS 6.0 and earlier (here, 5.0.7), add
"environment": {"APPTAINERENV_RAS_COMMANDER_WINE_ACCEPT_TCU": "1"} (see
HEC-RAS 6.0 and earlier).
from ras_commander import RasApptainer, SshApptainerTransport
profile = RasApptainer.load_profile("my-site.json")
transport = SshApptainerTransport(profile)
# 1. Stage the complete project (terrain and other subfolders included) and submit.
prep = RasApptainer.render_prepare_job(
project_folder=r"C:\models\UPGU1", project_name="UPGU1", plan_number="01",
profile=profile, job_directory=r"C:\jobs\upgu1_p01_prepare",
)
prep = RasApptainer.submit(prep, transport=transport, profile=profile, dry_run=False)
# 2. When terminal, verify and assemble the prepared project (new or empty folder).
prepared = RasApptainer.collect_prepare(prep, transport, profile, r"C:\jobs\upgu1_p01_prepared")
if not prepared.success:
raise RuntimeError(prepared.problems)
# 3. Render and submit the native solve from the prepared project, exactly as above.
job = RasApptainer.render_job(
project_folder=prepared.project_folder, project_name="UPGU1", plan_number="01",
profile=profile, job_directory=r"C:\jobs\upgu1_p01",
)
The prepare job verifies the executor SIF and input hashes, copies the project to node scratch, and
runs python -m ras_commander.remote.apptainer_prepare prepare in
apptainer exec --contain --cleanenv with a node-scratch /tmp. Inside the container it:
- copies the image's baked Wine prefix into
/tmp(the SIF stays read-only; no overlay or--writable-tmpfs), preloads the image's CPU-numbering shim, and disables the Wine debugger so a crash exits instead of hanging the allocation; - under Windows Python, sets the plan's solver cores to
num_cores(RasPlan.set_num_cores,RasPlan.set_2d_flow_options), clears stale geometry preprocessing, and runsRasPreprocess.preprocess_plan; - checks that
.p##.tmp.hdf,.b##, and.x##exist and passcheck_solver_ready, and writesprepare_receipt.jsonwith their hashes.
Only files the preparation changed or created are copied back, hash-verified, to out/<jobid>.
collect_prepare requires scheduler state COMPLETED, a matching SHA256SUMS, and a succeeded
receipt bound to this request whose artifact hashes match the downloaded files. Only then does it
write the prepared project: the staged tree overlaid with the changed files. The source project is
never modified. Failed prepare jobs are collected too (logs/prepare.log), and their node scratch
is retained for diagnosis.
Keep everything the model needs inside project_folder; external terrain or DSS paths are not
followed. Use RasApptainer.check_solver_ready and the normal solve-job render checks as the
handoff gate; they run unchanged on the prepared files.
Qualification status
CLB has run RasPreprocess.preprocess_plan under Wine inside Apptainer on Slurm with the same
per-task prefix pattern, then solved the prepared models natively. On one model, RasMapper's
direct property-table API crashed under Wine while RasPreprocess.preprocess_plan succeeded,
which is why the prepare job uses the latter. On October 13, 2026 render_prepare_job and
collect_prepare prepared Muncie plan 03 (2D) on the CLB cluster in the 6.6 executor SIF built
from containers/hecras-wine-executor (34 s, CPU shim loaded) and the prepared project solved
natively as described above; on October 14 the same ran for 5.0.7, 6.1, 6.5, 7.0, and 7.0.1.
That is one model per release; run a representative model end to end on your cluster before
relying on them.
The engine command¶
The only code that knows how the solver is invoked is ENGINE_SCRIPT_TEMPLATE in
ras_commander/RasApptainer.py. It mirrors RasCmdr.compute_plan_linux: io.* aliases,
LD_LIBRARY_PATH, then RasUnsteady <project>.p##.tmp.hdf x## for 6.3.1+, or the pre-6.3.1
cfile form: RasUnsteady <project>.c## b## (6.1, canonical layout) or rasUnsteady64
<project>.c## b## (5.0.7 bin_ras layout). The x/c suffix comes from the
plan's geometry (Geom File=g##), not from the plan number. The job checks the completion banner
and fatal markers before promoting the tmp HDF. Collection also requires the full
RasCmdr._validate_linux_solve fatal-marker/result check, populated /Results/Unsteady, and the
final Time Date Stamp matching the planned end time before reporting success. It collects
engine.log and the Slurm stdout file when available. If your image differs, adjust the profile (hecras_dir, ld_library_path,
geom_preprocess) or that one template. Scheduler stdout and the logs/ directory are retained
on all job failure paths; engine.log exists only after node-scratch staging begins.
Limits¶
- 2D/1D unsteady only, from Windows pre-computed artifacts (workstation or prepare job); no steady flow, no geometry-only runs. Steady and one-core unsteady request pools use RasSlurm, which also supports job arrays.
- One plan per solve job. Chained models (for example passing DSS output from one model into the next) are not implemented; they need a qualified output/readback boundary, not just Slurm dependencies.
- A prepare job and its solve job are two submissions with a local collection in between, so the solve job's render-time checks run on the actual prepared files.
- A pre-submission staging interruption may retry its unique attempt; an ambiguous
sbatchresult must be reconciled before a new render. - Offline tests cover rendering, validation, receipts and a mocked transport. Qualify the image and solver on your cluster before relying on results.
End-to-end example¶
This example shows a public example project's path from preprocessing to Slurm collection. It is a procedure, not a live qualification of the model, cluster, or canonical image. Use HEC-RAS 6.6 throughout. Before relying on a campaign, qualify the selected SIF and a representative prepared model on your cluster.
1. Install and check prerequisites¶
The control host needs Python, this API, and OpenSSH/scp. The primary preparation recipe below uses a Windows host with installed HEC-RAS 6.6; completed artifacts can later be rendered and submitted from Windows or Linux. To use source APIs, from a repository checkout in an activated isolated environment:
CI=1 skips the maintainer knowledge-base generation step during installation.
On Windows, set it using your shell's environment syntax before running
uv pip install -e ".[compute]" h5py. The optional Wine recipe requires Docker
Engine or Windows Docker Desktop in Linux-container mode. The
cluster needs Slurm accounting commands, Apptainer, a shared writable staging
root, and writable node-local scratch. Verify SSH host keys before using the
transport and populate the profile's known_hosts_file.
Pull the 6.6 native image to a shared SIF once using the command above, or pull its immutable registry reference. Record the resulting SIF hash. A registry digest and a SIF hash are different identities; jobs verify the actual SIF bytes.
2. Make a fresh model copy and preprocess¶
The public example library includes Muncie. Extract a source copy, then make a separate working copy. Choose an unsteady plan from its plan table:
import shutil
from pathlib import Path
from uuid import uuid4
from ras_commander import RasExamples, RasPreprocess, RasApptainer, init_ras_project
workspace = Path("ras-slurm-example") / uuid4().hex[:12]
source = RasExamples.extract_project("Muncie", output_path=workspace / "source")
working = workspace / "working" / source.name
shutil.copytree(source, working)
model = init_ras_project(working, "6.6", load_results_summary=False)
print(model.plan_df[["plan_number", "flow_type"]])
plans = model.plan_df.loc[model.plan_df["flow_type"] == "Unsteady", "plan_number"]
if plans.empty:
raise RuntimeError("Select an example with an unsteady plan")
plan_number = str(plans.iloc[0]).zfill(2)
project = Path(model.prj_file)
prepared = RasPreprocess.preprocess_plan(
plan_number, ras_object=model, max_wait=900, clear_existing=True,
)
if not prepared.success:
raise RuntimeError(prepared.error)
print(prepared.tmp_hdf_path)
Keep all referenced terrain, projection, and other dependencies reachable from
the working project. clear_existing=True clears stale preprocessing artifacts
in the working copy; keep the source and external dependencies unchanged.
Never stage or inspect an HDF while its writer is active.
Alternative: Wine preparation on the cluster¶
To keep preparation on Linux, skip the Windows call above and use
render_prepare_job and collect_prepare; then continue at
step 3 with prepared.project_folder. On a Linux workstation with Docker,
RasDocker.preprocess_plan(..., image="rascommander/hec-ras-wine-precompute_6.6:v4") is the
equivalent single-host route.
For either preparation route, check the completed local HDF before rendering:
tmp_hdf = project.parent / f"{project.stem}.p{plan_number}.tmp.hdf"
problems = RasApptainer.check_solver_ready(tmp_hdf, geom_preprocess=False)
if problems:
raise RuntimeError("Complete Windows preprocessing before staging: " + "; ".join(problems))
If the prepared output lacks /Geometry/GeomPreprocess, stop and use the native
Windows RasPreprocess route on a fresh working copy. Do not synthesize the
missing group or disable input checks. Passing this offline check establishes
structural readiness only.
3. Configure the site and render¶
Copy the example profile to my-site.json and replace all site settings:
- Use the verified SSH host/user/key and known-hosts file.
- Set shared
scratch_root, node-local scratch, and the shared SIF path. - Set
apptainer_image_sha256to the real SIF hash andcontainer_identitytosif:sha256:<that-hash>. - Choose the site's account, partition, memory, wall time, and core count.
Remove the example's
nodelistunless that node exists at your site. - Keep
geom_preprocess=falsefor the published image, which does not containRasGeomPreprocess. The completed temporary HDF must already contain it.
profile = RasApptainer.load_profile("my-site.json")
job_directory = workspace / "jobs" / f"plan-{plan_number}"
job = RasApptainer.render_job(
project_folder=project.parent, project_name=project.stem,
plan_number=plan_number, profile=profile, job_directory=job_directory,
)
print(job.script_path)
print(job.remote_directory)
RasApptainer.submit(job, dry_run=True)
Expect inputs/, engine.sh, job.sh, and job.json. Rendering and dry-run
submission do not contact the cluster. Review the resource directives and input
manifest. An all-zero image hash is permitted only for this offline step.
4. Submit and inspect status¶
from ras_commander import SshApptainerTransport
transport = SshApptainerTransport(profile)
job = RasApptainer.submit(job, transport=transport, profile=profile, dry_run=False)
print(job.slurm_job_id)
state = RasApptainer.status(job, transport, profile)
print(state.state, state.terminal, state.reason)
Real submission uploads this attempt and calls sbatch; it consumes the site's
allocation. Keep the recorded job ID and job.json. Query status later rather
than submitting again because the job is pending.
5. Reload, collect, and inspect results¶
from ras_commander import HdfResultsPlan
job = RasApptainer.load_job(job_directory)
state = RasApptainer.status(job, transport, profile)
if state.terminal:
result = RasApptainer.collect(job, transport, profile)
print(result.directory, result.success, result.problems)
if not result.success:
raise RuntimeError(result.problems)
final_hdf = result.directory / "project" / f"{job.project_name}.p{job.plan_number}.hdf"
print(HdfResultsPlan.get_unsteady_summary(final_hdf))
else:
print("Wait for a terminal scheduler state before collecting", state.reason)
A successful collection is rooted at job_directory/collected/<jobid>.
Its project/ subfolder contains the final plan HDF and solver.log,
logs/ contains available engine/scheduler logs, and receipt.json is at
that collection root. The collection
checks the planned final time, populated unsteady results, receipt identity,
and every output hash. Inspect water-surface results and diagnostics through
HDF modules, then review hydraulic suitability separately.
Use a new destination= for another collection; an existing destination is
rejected. To stop the recorded job, call
RasApptainer.cancel(job, transport, profile).
Troubleshooting¶
| Symptom | Meaning and next action |
|---|---|
InputCheckError before staging |
Preparation is incomplete or structurally invalid. Finish preprocessing and inspect listed issues; do not disable checks to bypass missing solver inputs |
| SIF digest mismatch | The shared image differs from the recorded profile. Preserve the failed evidence and verify the intended image before changing the profile |
| Pending allocation | Inspect the site's resource/account/partition reason. Pending does not justify another submission |
UNKNOWN status |
Accounting may be delayed or unavailable. Inspect the returned reason and reconcile with the cluster before collecting or submitting again |
STACK_LIMIT_FAILED |
Host or container stack limit could not be raised. Check the site's limits and image environment |
| Missing geometry preprocessing | Keep geom_preprocess=false for the canonical image and provide a completed /Geometry/GeomPreprocess group |
Prepare job PREPARE_FAILED |
Read logs/prepare.log and prepare_receipt.json (detail, worker). Check the executor's Wine environment variables, the Ras.exe path for ras_version, and that all model files are inside the project folder |
Intermittent 0xC0000005 in RasProcess.exe during preparation |
The allocation's CPU set does not start at CPU 0 and the executor lacks the CPU shim; rebuild it from the current recipe |
Submission remains SUBMITTING after a transport error |
sbatch may have accepted it. Reconcile the recorded attempt on the cluster; do not retry blindly |
| Failed receipt, output hash, or final-time validation | Retain logs and receipt; inspect result.problems. A file's existence or scheduler COMPLETED alone does not establish a successful solve |
| Failed-job outputs cannot be downloaded | Inspect the exact remote attempt and retained logs/; failure before scratch staging may produce no engine.log or output receipt |
The API reference documents profile, transport, job, and collection records.