Docker preprocessing and native Linux computation¶
Use RasDocker.preprocess_plan() and RasDocker.compute_plan() from the same Python program on Windows or Linux. The first call prepares the plan with Windows HEC-RAS under Wine. The second runs the matching official HEC-RAS Linux unsteady solver. Both containers use ras-commander to drive HEC-RAS; the containers supply its runtime and manage files, processes, and receipts.
The host needs Python with this version of the library and a working Docker CLI connected to a Linux container engine. On Windows, use Docker Desktop with Linux containers. HEC-RAS, Wine, and Windows Python are included in the preprocessing image; a separate host HEC-RAS installation or Wine profile mount is unnecessary.
Install the host API and notebook inspection dependencies in an activated Python environment:
The image contains its own installed library. This command installs the host API used to launch the containers and inspect their outputs. This host revision accepts HEC-RAS 6.5, 6.6, and 7.0.1. Image qualification and publication status are listed below.
Published native images install library source 604704d440c49a39d6f6e8bae262e2233d895dd0. Wine controllers use dc60b219091e85bcb4564eca45313475c39ce58a, with Windows library source 9e4217713e954236b0c16023e1815c6f2b7a5309. These revisions identify the published image payloads; the host API follows the installed ras-commander release.
Select matching images¶
Qualification status: All three matching versions passed the full 266-hour Linux sample and the one-hour Windows Docker Desktop notebook, using two CPUs per container. Linux results contained 267 output times; Windows results contained two, with 6,548 finite water-surface values at every time. Live progress, resume and sequential batch checks passed on both hosts; the six Linux Wine LF/CRLF cases also passed. The images are published on Docker Hub, and anonymous pulls verified all six matching payloads.
| HEC-RAS | Wine preprocessing image | Native unsteady image |
|---|---|---|
| 6.5 | rascommander/hec-ras-wine-precompute_6.5:v4 |
rascommander/hec-ras-linux-unsteady_6.5:v1 |
| 6.6 | rascommander/hec-ras-wine-precompute_6.6:v4 |
rascommander/hec-ras-linux-unsteady_6.6:v1 |
| 7.0.1 | rascommander/hec-ras-wine-precompute_7.0.1:v4 |
rascommander/hec-ras-linux-unsteady_7.0.1:v1 |
Pull both matching images before the first run to refresh the local tags:
docker pull rascommander/hec-ras-wine-precompute_6.5:v4
docker pull rascommander/hec-ras-linux-unsteady_6.5:v1
For 6.6 or 7.0.1, change the version in both repository names. Wine v4 and native v1 also have matching latest tags. The API examples use pull="always" to refresh the selected image.
Use matching HEC-RAS versions. A missing image fails explicitly; the API does not choose another version. The native worker requires populated 2D meshes and validates their complete water-surface output.
Prepare a complete working copy¶
Start with a fresh, complete model copy containing its original populated geometry HDF. Keep the source project separate. A previous failed run that lost its 2D geometry is not a valid preprocessing baseline.
For the ras2fim sample, keep the following relative layout:
02_model_copies/
<model-name>/
<model-name>.prj
<model-name>.p01
<model-name>.g01
<model-name>.g01.hdf
<model-name>.u01
<model-name>.u01.hdf
<model-name>.rasmap
... other original model files ...
source_terrain/
Terrain.hdf
... every terrain TIFF or other referenced raster ...
projection/
EPSG_2277.prj
The selected plan determines the geometry and unsteady-flow numbers; they
need not all be 01. The terrain HDF alone is insufficient when it references
separate raster files. For another project, supply the dependencies declared
by that project's plan, map, and terrain files.
The project directory is mounted read/write at /job. In the sample,
references to ..\\source_terrain and ..\\projection resolve through additional
read-only mounts at /source_terrain and /projection. Mounting just the
project folder does not expose its sibling directories. All host paths must
be accessible to the Docker engine, not only to the Python process.
Call both phases¶
This code uses a working copy prepared beforehand. The example notebook includes a fresh-copy setup and result checks.
from pathlib import Path
from ras_commander import RasDocker
models = Path("/path/to/working/02_model_copies")
# On Windows, for example: Path(r"C:\Users\you\ras-runs\02_model_copies")
name = "your-model-name"
project = models / name / f"{name}.prj"
version = "6.5"
preprocess_image = f"rascommander/hec-ras-wine-precompute_{version}:v4"
compute_image = f"rascommander/hec-ras-linux-unsteady_{version}:v1"
prepared = RasDocker.preprocess_plan(
project,
"01",
version=version,
image=preprocess_image,
mounts={
"/source_terrain": models / "source_terrain",
"/projection": models / "projection",
},
timeout=900,
num_cores=2,
replace_generated=True,
pull="always",
)
print(prepared.receipt_path)
if not prepared.success:
raise RuntimeError(prepared.error or prepared.receipt)
computed = RasDocker.compute_plan(
project,
"01",
version=version,
image=compute_image,
prepare_receipt=prepared.receipt_path,
timeout=14400,
num_cores=2,
pull="always",
)
print(computed.receipt_path)
if not computed.success:
raise RuntimeError(computed.error or computed.receipt)
replace_generated=True authorizes preprocessing to replace generated model
files, including existing plan outputs. Use it on the working copy. Stop on
a failed preprocessing result; do not pass an incomplete preparation to
the solver. Each call returns a result with success, receipt_path,
receipt, stdout, stderr, returncode, and error for programmatic inspection.
The error field explains host-side failures, such as a failed image pull;
receipt contains the container diagnostics when a receipt was produced.
CPU limits, progress, resume, and batch summaries¶
The pinned host API and native source above provide these features. The container runs one selected plan at a time; scheduling multiple models stays on the host.
Match solver cores to container resources¶
num_cores defaults to 2 and accepts integers from 1 through 8.
RasDocker passes it as Docker's --cpus for both stages and
as --num-cores for each stage. The native worker passes the count
to RasCmdr.compute_plan_linux(), which sets the plan/HDF core
settings and OMP_NUM_THREADS/MKL_NUM_THREADS.
Docker's CPU limit controls aggregate CPU time. It does not reserve exclusive
physical cores or pin the process; CPU affinity is a separate setting.
Resource limits belong in the launch command, while the Dockerfile defines
the installed environment. See Docker CPU constraints.
The Wine worker calls RasPlan.set_num_cores() and
RasPlan.set_2d_flow_options() before preprocessing. The second
call uses include_default=True and inserts missing default or named-mesh
processor settings. It reads the setting back with RasPlan.get_plan_value(), so the selected
plan's processor count matches the container quota.
Both receipts record the effective count in arguments.num_cores. Resume
requires that recorded count to match the new request.
Each container runs one selected plan. Running several model containers is the host or scheduler's responsibility. For example, four simultaneous jobs at two cores each need eight CPU equivalents plus sufficient memory and scratch storage for all four models.
Receive live output and lifecycle callbacks¶
Use the same partial ExecutionCallback pattern as
RasCmdr, or implement on_container_event to distinguish
projects, plans, stages and output streams:
class Progress:
def on_container_event(self, event):
label = f"{event.project_path.stem} / {event.plan_number} / {event.stage}"
print(f"[{label}] {event.kind}: {event.message}", flush=True)
computed = RasDocker.compute_plan(
project, "01", version=version, num_cores=2,
image=compute_image, pull="always",
prepare_receipt=prepared.receipt_path, replace_generated=True,
stream_callback=Progress(), resume=True,
)
Events are start, message, complete, or resumed. Messages preserve
their stdout/stderr source and arrive as the container emits them.
complete.success includes the container receipt check. A resumed stage
emits resumed and does not pretend that a new process ran.
The compatible methods are on_prep_start, on_prep_complete,
on_exec_start, on_exec_message, on_exec_complete, and on_verify_result.
Preparation completion is emitted only on success; native completion includes
the success flag. For this API, verification means the container's validation
receipt passed. An ordinary callback exception is logged without failing the
model; KeyboardInterrupt propagates after owned-container cleanup. Keep
callbacks quick, and use project-aware events when different models share a
plan number.
The native worker forwards its solver log while computation is running, handling CRLF, LF and lone-CR progress records. Live callbacks matched the complete ordered solver log in all three Linux qualification runs. Timing depends on when HEC-RAS flushes its output; the API does not fabricate percentages during silent periods.
Resume completed stages¶
Enable resume=True on the first and subsequent calls. After a successful
stage, the host saves a resume record under .ras-commander/resume/. A later
call reuses it only if the model inputs, read-only dependency contents,
version, image reference, core count, receipt, and output artifacts match.
Model/dependency contents are read to check this, which can be expensive for
large terrain datasets. The default resume=False does not build that extra
inventory. Use an immutable image reference when reproducibility must include
the exact image contents; resume compares the requested image reference and
does not contact the registry to resolve a mutable tag.
This resumes the workflow at completed-stage boundaries; it is not a HEC-RAS
restart from a partially computed simulation. A failed or changed stage runs
normally, and replace_generated still controls whether existing outputs
can be replaced. Existing receipts created before resume was enabled are
insufficient by themselves. A cache hit returns resumed=True, the original
receipt, and returncode=None because no Docker process ran. It also skips
pulling an image, regardless of the requested pull policy.
Collect a batch summary¶
RasDocker.run_batch() processes working copies sequentially on the host, records each outcome, and continues after ordinary job failures:
jobs = [
{"project_path": models / "model-a" / "model-a.prj", "plan_number": "01"},
{"project_path": models / "model-b" / "model-b.prj", "plan_number": "01"},
]
batch = RasDocker.run_batch(
jobs, stage="run", version=version, num_cores=2,
preprocess_image=preprocess_image, compute_image=compute_image, pull="always",
mounts={"/source_terrain": models / "source_terrain",
"/projection": models / "projection"},
resume=True, stream_callback=Progress(),
)
display(batch.summary_df)
batch.summary_df.to_csv(models / "batch-summary.csv", index=False)
Use stage="prepare" or stage="compute" for a single phase. Each job can
override shared options. batch.results retains the per-job preparation and
computation results; summary_df includes status, reuse, elapsed time,
receipt paths and errors, including invalid jobs. A failed preparation skips
that job's computation. An interrupt stops the batch and cleans up the active
container. Resume records remain available for a later invocation.
For TACC, use an external scheduler to distribute independent runs. The implementation comparison and TACC design notes describe the proposed Apptainer/Slurm integration and the staging changes needed in ras2fim. That backend has not yet been implemented or qualified.
What runs inside each container¶
flowchart TD
P["Host Python: RasDocker.preprocess_plan()"] --> W["Wine container: init_ras_project()"]
W --> G["RasPlan.get_plan_path()"]
G --> CPU["RasPlan.set_num_cores()"]
CPU --> CPU2D["RasPlan.set_2d_flow_options(cores=N, include_default=True)"]
CPU2D --> CHECK["RasPlan.get_plan_value(): verify requested cores"]
CHECK --> C["GeomPreprocessor.clear_geompre_files()"]
C --> F["RasPlan.update_run_flags()"]
F --> PRE["RasPreprocess.preprocess_plan()"]
PRE --> H["Windows HEC-RAS under Wine"]
H --> T["Validated .p01.tmp.hdf, .b01 and geometry inputs on host"]
T --> D["Host Python: RasDocker.compute_plan()"]
D --> S["Native container: private Linux project copy"]
S --> INIT["init_ras_project()"]
INIT --> PLAN["RasPlan.get_plan_value(): geometry and time window"]
PLAN --> N["RasCmdr.compute_plan_linux(retry=False, num_cores=N)"]
N --> R["Official Linux RasUnsteady"]
R --> E["RasCmdr.inspect_execution_evidence(): supplementary observations"]
E --> V["Validate results and completed simulation time"]
V --> O["Publish .p01.hdf and compute receipt to host"]
O --> Q["HdfResultsMesh.get_mesh_timeseries()"]
Inspect the actual API implementations: RasDocker, Wine init_ras_project(), Wine RasPlan.get_plan_path() and RasPlan.update_run_flags(), RasPlan.set_num_cores(), RasPlan.set_2d_flow_options() and RasPlan.get_plan_value(), GeomPreprocessor.clear_geompre_files(), RasPreprocess.preprocess_plan(), native init_ras_project(), native RasPlan.get_plan_value(), RasCmdr.compute_plan_linux() and RasCmdr.inspect_execution_evidence(), and HdfResultsMesh.get_mesh_timeseries(). The preprocessing worker and native container worker show how these calls are connected.
Wine and Windows preprocessing¶
Wine implements the Windows interfaces needed by the installed HEC-RAS
application within Linux. The image contains a prepared Windows directory and
registry tree, called a Wine prefix, under /runtime/wine-seed/prefix.
Its drive_c directory contains Windows Python and the installed HEC-RAS
application. /runtime/wine-seed/runtime.json identifies the matching HEC-RAS
version and its installed paths. For version V, the executable is
C:\Program Files (x86)\HEC\HEC-RAS\V\Ras.exe, backed by
/runtime/wine-seed/prefix/drive_c/Program Files (x86)/HEC/HEC-RAS/V/Ras.exe.
Windows Python is C:\Python311\python.exe.
Each run copies the profile into /run/ras-job/<run-id>/wineprefix, its
private writable scratch space. Windows
Python runs the preprocessing worker, which calls the linked
ras-commander APIs. HEC-RAS reads and writes the
host working model through /job; Wine provides Windows access to those
mounted Linux paths. The profile template remains reusable between runs.
In the standard API launch, /run/ras-job and /tmp use the container's
writable layer. Docker removes that layer with --rm; the host bind mounts
remain. The root-owned seed is protected from UID 1000, while root could write
it; the controller directs ordinary job changes to its copied prefix. The
ras2fim launcher
separately configures a read-only root, temporary-memory mount and scratch volume.
Preflight checks the project and its dependencies before changing model files. Selected model text is normalized to Windows CRLF line endings, including when it was created on Linux. The worker forces geometry preprocessing and checks the generated geometry and temporary plan HDFs for retained 2D areas, cell counts, and populated hydraulic property tables before reporting success.
Native Linux unsteady computation¶
The second image contains the official Linux solver and its shared libraries
at /opt/hecras-runtime/engine/RasUnsteady and
/opt/hecras-runtime/engine/libs/, with settings in
/opt/hecras-runtime/runtime.json. It does not use Wine. The
container worker stages a private project copy on Linux
storage and calls RasCmdr.compute_plan_linux(). That API handles
the Linux solver's runtime library environment and io.* file aliases.
Private Linux storage allows those aliases and solver scratch writes to work
consistently even when /job comes from a Windows drive. Scratch text uses Linux LF line endings; binary HDFs are not text-normalized.
The host .p01.tmp.hdf, .b01 and .x01 preparation artifacts are preserved. After a successful calculation and result checks,
the worker publishes the final .p01.hdf back through the host mount. A
failed calculation is recorded in its receipt; an unvalidated result is not
promoted to the final host result path.
The worker checks that the compiled inputs still belong to the successful preparation. If the plan or prepared files change, run preprocessing again.
Inspect preparation and results separately¶
A prepared .p01.tmp.hdf contains geometry and boundary data for the next
phase. It is not expected to contain /Results. File size alone cannot prove
that preparation succeeded. Inspect the preparation receipt and use
HdfBase.get_2d_flow_area_names_and_counts(),
HdfMesh.get_mesh_cell_property_tables() and
HdfMesh.get_mesh_face_property_tables() to inspect its content.
The final .p01.hdf should contain time-dependent unsteady results. Use
HdfResultsPlan.get_unsteady_summary() for reported
completion information and HdfResultsMesh.get_mesh_timeseries()
for finite water-surface values and timestamps. Check every value for
finiteness and require the observed time extent to match exactly with
HdfPlan.get_plan_start_time() and HdfPlan.get_plan_end_time().
Collection metadata can report a different cell count from the number of
stored coordinate rows. The sample reports 6,201 cells but stores 6,548
coordinate rows and water-surface columns. The notebook uses
HdfMesh.get_mesh_sloped_topology() to compare the result-array
width with the actual prepared coordinate count, while also checking that
collection metadata stays unchanged.
The notebook demonstrates these checks with the real sample; it does not use
the presence of /Results or a zero process exit code as the sole success test.
The updated native worker also retains
RasCmdr.inspect_execution_evidence() observations in the compute
receipt's execution_evidence field. Those observations remain separate from
the native acceptance checks: the HDF completion attribute can already be true
in a prepared .tmp.hdf, and native results can omit the Windows
Complete Process message. A generic completion flag alone therefore cannot
prove the native stage finished. An unreadable diagnostic channel is recorded
as inspection_error; the solver log, full output window, dimensions, and
finite-value checks still determine whether the result can be published.
Receipts are retained below .ras-commander/runs/<run-id>/ in the working
project. Keep them with the resulting model when reporting a problem. The
notebook demonstrates execution mechanics; model suitability and interpretation
of hydraulic results remain engineering decisions.
For instructions to build from the source checkout and an external vendor runtime, see How to re-create the native container. The existing Wine container operating guide documents its corresponding installed runtime and build inputs. For 7.0.1, extract_installer.py documents extraction of the official combined installer into the native build inputs.