Skip to content

Docker Precompute and Native Linux Compute

Run the same two Python calls on Windows or Linux: first prepare a HEC-RAS plan with the Wine container, then execute the matching native Linux unsteady container. Both containers use ras-commander APIs to drive HEC-RAS. The host needs a Linux Docker engine and Python; it does not need its own HEC-RAS installation.

This example uses the real ras2fim 50-cfs sample and retains a fresh working copy, preparation receipt, computation receipt, and final result HDF. It demonstrates execution mechanics and result inspection, not a prescribed engineering workflow. No model data or executed outputs are committed with this notebook.

Install and select the runtime

Install the pinned host API and notebook inspection dependencies in an activated Python environment:

Bash
uv pip install "ras-commander[compute]" jupyterlab matplotlib xarray geopandas

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 that Python environment as this notebook's kernel. Start Docker Desktop in Linux-container mode on Windows, or Docker Engine on Linux.

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:

Bash
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.

Python
USE_LOCAL_SOURCE = False

import json
import os
import shutil
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from uuid import uuid4

if USE_LOCAL_SOURCE:
    local_source = Path.cwd() if (Path.cwd() / "ras_commander").is_dir() else Path.cwd().parent
    sys.path.insert(0, str(local_source))

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from IPython.display import display

from ras_commander import (
    HdfBase, HdfMesh, HdfPlan, HdfResultsMesh, HdfResultsPlan, HdfUtils,
    RasDocker, RasPlan, init_ras_project,
)

Obtain the complete source sample

Browse the ras2fim sample data, described in the ras2fim README. Download the sample_output/02_model_copies data needed for the selected model, including its source_terrain and projection siblings. The sample data are hosted separately from Git.

Set SOURCE_MODELS to that 02_model_copies directory and WORK_ROOT to a short, writable directory shared with Docker. On Windows, a local drive such as C:/Users/you/ras-runs is appropriate. On Linux, use a local directory such as /home/you/ras-runs. Neither path should point into the installed container.

To select the published images explicitly, set RAS_DOCKER_PREPROCESS_IMAGE=rascommander/hec-ras-wine-precompute_6.5:v4, RAS_DOCKER_COMPUTE_IMAGE=rascommander/hec-ras-linux-unsteady_6.5:v1, and RAS_DOCKER_PULL_POLICY=always before starting Jupyter. Set these as environment variables using PowerShell on Windows or your shell on Linux. For another version, change RAS_DOCKER_VERSION and both image references together. These overrides select the published images without changing this notebook's code.

Keep the full original model folder, its populated .g01.hdf, the terrain HDF and all referenced terrain TIFFs, and the projection file. Do not use a damaged model folder left by a failed preprocessing run.

Python
SOURCE_MODELS = Path(os.environ.get(
    "RAS_DOCKER_SOURCE_MODELS", "/path/to/sample_output/02_model_copies"
))
WORK_ROOT = Path(os.environ.get("RAS_DOCKER_WORK_ROOT", str(Path.home() / "ras-docker-runs")))
MODEL_NAME = "1919912_wb-2427466_wb-2427467_14-hr_50-cfs_to_11609-cfs"
PLAN = "01"
VERSION = os.environ.get("RAS_DOCKER_VERSION", "6.5")  # Same version for both phases.
if VERSION not in {"6.5", "6.6", "7.0.1"}:
    raise ValueError("VERSION must be 6.5, 6.6, or 7.0.1.")
PULL_POLICY = os.environ.get("RAS_DOCKER_PULL_POLICY", "always")
# None selects the published matching-version image; use overrides for local builds.
PREPROCESS_IMAGE = os.environ.get("RAS_DOCKER_PREPROCESS_IMAGE")
COMPUTE_IMAGE = os.environ.get("RAS_DOCKER_COMPUTE_IMAGE")
# None runs the full plan; 1 runs its first simulation hour in the working copy.
DEMO_HOURS = int(os.environ["RAS_DOCKER_DEMO_HOURS"]) if os.environ.get("RAS_DOCKER_DEMO_HOURS") else None
if DEMO_HOURS is not None and DEMO_HOURS < 1:
    raise ValueError("DEMO_HOURS must be a positive whole number of simulation hours.")
PREPARE_TIMEOUT = 900
COMPUTE_TIMEOUT = 14400
NUM_CORES = int(os.environ.get("RAS_DOCKER_NUM_CORES", "2"))
if not 1 <= NUM_CORES <= 8:
    raise ValueError("NUM_CORES must be from 1 through 8.")

SOURCE_MODELS = SOURCE_MODELS.expanduser().resolve()
WORK_ROOT = WORK_ROOT.expanduser().resolve()
source_project = SOURCE_MODELS / MODEL_NAME / f"{MODEL_NAME}.prj"
required_sources = [source_project, SOURCE_MODELS / "source_terrain", SOURCE_MODELS / "projection"]
missing = [str(path) for path in required_sources if not path.exists()]
if missing:
    raise FileNotFoundError("Configure SOURCE_MODELS with a complete sample tree: " + ", ".join(missing))

Create a fresh working copy

This cell copies the complete selected model and its dependencies into a new run folder. Rerunning it makes a separate folder; it does not overwrite the source or an earlier run. Preprocessing is allowed to replace generated files inside this copy.

Text Only
<run-root>/
  <model-name>/        -> /job                  (read/write)
  source_terrain/      -> /source_terrain       (read-only)
  projection/          -> /projection           (read-only)

The separate dependency mounts preserve ..\source_terrain and ..\projection references. Mounting only /job would hide those sibling folders.

Python
run_name = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + "-" + uuid4().hex[:8]
run_root = WORK_ROOT / run_name
run_root.mkdir(parents=True, exist_ok=False)
for name in (MODEL_NAME, "source_terrain", "projection"):
    shutil.copytree(SOURCE_MODELS / name, run_root / name)

project = run_root / MODEL_NAME / f"{MODEL_NAME}.prj"
dependency_mounts = {
    "/source_terrain": run_root / "source_terrain",
    "/projection": run_root / "projection",
}
print(f"Working project: {project}")

Optional short demonstration

DEMO_HOURS=None preserves the complete source plan. To exercise the workflow more quickly, set DEMO_HOURS=1 in the configuration or set the environment variable RAS_DOCKER_DEMO_HOURS=1. This changes only the working copy to its first simulation hour before preprocessing. It demonstrates container operation; it does not calculate the full source plan.

The cell reads the plan through RasPlan.get_plan_value(), parses its dates with HdfUtils.parse_ras_window_datetime(), and changes the requested window through RasPlan.update_simulation_date(). Use a whole number of simulation hours for this sample's hourly output interval.

Python
project_data = init_ras_project(
    project, ras_version=VERSION, ras_object="new",
    load_results_summary=False, load_hdf_metadata=False, hide_intro=True,
)
plan_path = RasPlan.get_plan_path(PLAN, ras_object=project_data)
if plan_path is None:
    raise RuntimeError(f"Plan {PLAN} is not present in the working project.")
window_tokens = RasPlan.get_plan_value(plan_path, "Simulation Date", ras_object=project_data).split(",")
source_start = HdfUtils.parse_ras_window_datetime(window_tokens[0] + " " + window_tokens[1].replace(":", ""))
source_end = HdfUtils.parse_ras_window_datetime(window_tokens[2] + " " + window_tokens[3].replace(":", ""))
if DEMO_HOURS is not None:
    demo_end = source_start + timedelta(hours=DEMO_HOURS)
    if demo_end > source_end:
        raise ValueError("DEMO_HOURS exceeds the source plan's simulation window.")
    RasPlan.update_simulation_date(plan_path, source_start, demo_end, ras_object=project_data)
print(RasPlan.get_plan_value(plan_path, "Simulation Date", ras_object=project_data))

Follow the actual API calls

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()"]

Wine supplies Windows interfaces so the library can call the installed Windows HEC-RAS application. The image bundles /runtime/wine-seed/prefix, with HEC-RAS at C:\Program Files (x86)\HEC\HEC-RAS\V\Ras.exe and Windows Python at C:\Python311\python.exe. Each run copies that profile into private scratch at /run/ras-job/<run-id>/wineprefix.

In the standard API launch, this scratch and /tmp use the container's writable layer. Docker removes that layer with --rm, while the host files at /job remain. The worker copies the root-owned seed for each run; a root container could still modify the template path.

The native container calls /opt/hecras-runtime/engine/RasUnsteady and its vendor libraries directly. Its private Linux working directory supports the solver's io.* aliases even when the host model is on a Windows drive. Both stages use the host's /job bind mount: preprocessing writes prepared inputs there, and native computation stages a private copy before returning a validated final HDF through the same mount. Read-only terrain and projection mounts provide the sample's dependencies.

Inspect the host RasDocker.preprocess_plan() and RasDocker.compute_plan(). The Wine worker uses the installed Windows library's init_ras_project(), RasPlan.get_plan_path(), set_num_cores(), set_2d_flow_options(), get_plan_value() and update_run_flags(), GeomPreprocessor.clear_geompre_files(), and RasPreprocess.preprocess_plan(). The native worker uses init_ras_project(), RasPlan.get_plan_value(), and RasCmdr.compute_plan_linux() and inspect_execution_evidence(). The Wine worker and native worker connect these calls inside the images. Each source link identifies the code installed for that stage.

Optional progress events and reuse of completed stages

ContainerEvent and the callback parameters expose stage start, actual stdout/stderr messages, validated completion, and reuse events. A partial callback object only needs the methods you use; the example below implements on_container_event. Set ENABLE_LIVE_PROGRESS=False to suppress its printing. The display prints at most one actual message per stage every five seconds; the API still delivers all messages and retains the full logs. It does not estimate a simulation percentage from elapsed time.

The native worker forwards actual solver log lines while computation runs. Linux qualification verified the complete ordered callback stream for all three native images.

NUM_CORES defaults to 2 and accepts integers 1 through 8. The host passes the same count to Docker's CPU quota and both container workers. Wine sets and reads back the plan's unsteady, default 2D and named 2D core settings; native computation applies the plan/HDF settings and solver thread environment. Both receipts record arguments.num_cores. One container handles one plan; model concurrency is a separate host/scheduler decision.

Set RESUME_COMPLETED_STAGES=True before the first run to record reusable successful stages. Repeating the same stage against the unchanged working copy can then return result.resumed=True after checking its inputs, outputs, and receipt. This is reuse of a completed stage, not a restart partway through a hydraulic simulation. Failed or interrupted stages are not resumed. Preserve run_root and rerun the stage cells; rerunning the fresh-copy cell creates a different project that cannot reuse the earlier run.

Python
import time

ENABLE_LIVE_PROGRESS = True
RESUME_COMPLETED_STAGES = False  # Opt in before running either phase.

class NotebookContainerProgress:
    def __init__(self):
        self._last_print = {}

    def on_container_event(self, event):
        label = f"{event.project_path.stem} / plan {event.plan_number} / {event.stage}"
        if event.kind == "message":
            key = (event.project_path, event.plan_number, event.stage)
            now = time.monotonic()
            if now - self._last_print.get(key, float("-inf")) < 5:
                return
            self._last_print[key] = now
            print(f"[{label}] {event.stream}: {event.message}", flush=True)
        elif event.kind in {"complete", "resumed"}:
            print(f"[{label}] {event.kind}: success={event.success}", flush=True)
        else:
            print(f"[{label}] {event.kind}", flush=True)

progress_callback = NotebookContainerProgress() if ENABLE_LIVE_PROGRESS else None

Phase 1: prepare the plan under Wine

The call selects the configured image and pull policy, validates the model and dependency mounts, normalizes selected text files to Windows CRLF, and forces geometry preprocessing. The container checks the retained 2D areas, cell counts, and hydraulic property tables before reporting success.

replace_generated=True replaces generated artifacts, including existing plan outputs in the working copy. The source sample is unchanged.

Python
preparation = RasDocker.preprocess_plan(
    project,
    PLAN,
    version=VERSION,
    image=PREPROCESS_IMAGE,
    mounts=dependency_mounts,
    timeout=PREPARE_TIMEOUT,
    num_cores=NUM_CORES,
    replace_generated=True,
    pull=PULL_POLICY,
    stream_callback=progress_callback,
    resume=RESUME_COMPLETED_STAGES,
)
print(f"Preparation success: {preparation.success}")
print(f"Receipt: {preparation.receipt_path}")
print(json.dumps(preparation.receipt, indent=2, default=str))
if not preparation.success:
    print(preparation.error or preparation.stderr[-4000:])
    raise RuntimeError("Preprocessing failed; inspect its receipt before attempting Linux computation.")

Inspect the prepared temporary HDF

A preprocessing .p01.tmp.hdf is an input to the solver. It is not expected to have a /Results group. Its size alone is not a validation criterion. init_ras_project() and RasPlan.get_plan_path() resolve the selected plan, then HdfBase.get_2d_flow_area_names_and_counts() and HdfMesh.get_mesh_cell_property_tables() / HdfMesh.get_mesh_face_property_tables() expose its mesh and hydraulic tables. These are read-only host operations; they do not launch a host HEC-RAS installation.

HdfMesh.get_mesh_sloped_topology() supplies the actual coordinate-row count used to check result-array widths. Collection metadata counts and stored coordinate-row counts are shown separately.

Python
project_data = init_ras_project(
    project, ras_version=VERSION, ras_object="new",
    load_results_summary=False, load_hdf_metadata=False, hide_intro=True,
)
plan_path = RasPlan.get_plan_path(PLAN, ras_object=project_data)
if plan_path is None:
    raise RuntimeError(f"Plan {PLAN} is not present in the working project.")
prepared_hdf = Path(str(plan_path) + ".tmp.hdf")
result_hdf = Path(str(plan_path) + ".hdf")
Python
prepared_counts = dict(HdfBase.get_2d_flow_area_names_and_counts(prepared_hdf))
cell_tables = HdfMesh.get_mesh_cell_property_tables(prepared_hdf)
face_tables = HdfMesh.get_mesh_face_property_tables(prepared_hdf)
prepared_topology = {name: HdfMesh.get_mesh_sloped_topology(prepared_hdf, name) for name in prepared_counts}
prepared_coordinate_counts = {name: int(topology["n_cells"]) for name, topology in prepared_topology.items()}
if not prepared_counts or any(count <= 0 for count in prepared_counts.values()):
    raise RuntimeError("The prepared HDF has no populated 2D mesh.")
for mesh_name in prepared_counts:
    if prepared_coordinate_counts[mesh_name] < prepared_counts[mesh_name]:
        raise RuntimeError(f"Inconsistent prepared mesh coordinate count: {mesh_name}")
    if cell_tables.get(mesh_name, pd.DataFrame()).empty:
        raise RuntimeError(f"Missing cell elevation-volume table: {mesh_name}")
    if face_tables.get(mesh_name, pd.DataFrame()).empty:
        raise RuntimeError(f"Missing face elevation-area table: {mesh_name}")

display(pd.DataFrame([
    {"mesh": name, "reported_cells": count, "coordinate_rows": prepared_coordinate_counts[name],
     "cell_table_rows": len(cell_tables[name]), "face_table_rows": len(face_tables[name])}
    for name, count in prepared_counts.items()
]))
print(f"Prepared HDF: {prepared_hdf.name} ({prepared_hdf.stat().st_size:,} bytes)")

Phase 2: compute with the native Linux solver

Pass the successful preparation receipt explicitly so the two runs remain linked. This call stages the prepared project on private Linux storage, runs the matching native unsteady solver, and checks completion and result content. The host's prepared temporary HDF remains available for another run. Only validated results are published as the final .p01.hdf in the working model folder.

This cell starts the full selected plan. Runtime depends on the model, host, and CPU allocation; the timeout below permits up to four hours.

Python
computation = RasDocker.compute_plan(
    project,
    PLAN,
    version=VERSION,
    image=COMPUTE_IMAGE,
    prepare_receipt=preparation.receipt_path,
    timeout=COMPUTE_TIMEOUT,
    num_cores=NUM_CORES,
    pull=PULL_POLICY,
    stream_callback=progress_callback,
    resume=RESUME_COMPLETED_STAGES,
)
print(f"Compute success: {computation.success}")
print(f"Receipt: {computation.receipt_path}")
print(json.dumps(computation.receipt, indent=2, default=str))
if not computation.success:
    print(computation.error or computation.stderr[-4000:])
    raise RuntimeError("Native Linux computation failed; inspect the compute receipt and logs.")

Read actual water-surface results and time extent

Use HdfResultsPlan.get_unsteady_summary() for the reported summary and HdfResultsMesh.get_mesh_timeseries() for water-surface values. Keep truncate=False when checking simulation coverage so leading or trailing values are not silently trimmed. HdfPlan.get_plan_start_time() and HdfPlan.get_plan_end_time() provide the requested time window.

These containers require finite water-surface values throughout each mesh and output timestamps that exactly cover the requested plan window. The checks below enforce the same requirements and report the observed values for review. The sample stores 6,201 cells in collection metadata but 6,548 coordinate rows and water-surface columns; those are checked separately rather than assuming that the two counts mean the same thing.

Python
display(HdfResultsPlan.get_unsteady_summary(result_hdf))
result_counts = dict(HdfBase.get_2d_flow_area_names_and_counts(result_hdf))
if result_counts != prepared_counts:
    raise RuntimeError("Result mesh names or cell counts differ from the prepared model.")

expected_start = pd.Timestamp(HdfPlan.get_plan_start_time(result_hdf))
expected_end = pd.Timestamp(HdfPlan.get_plan_end_time(result_hdf))
water_surfaces = {}
rows = []
for mesh_name, cell_count in prepared_coordinate_counts.items():
    result_topology = HdfMesh.get_mesh_sloped_topology(result_hdf, mesh_name)
    if result_topology["n_cells"] != cell_count or not np.array_equal(
        result_topology["cell_centers"], prepared_topology[mesh_name]["cell_centers"]
    ):
        raise RuntimeError(f"Result mesh coordinates differ from preparation: {mesh_name}")
    wse = HdfResultsMesh.get_mesh_timeseries(
        result_hdf, mesh_name, "Water Surface", truncate=False
    )
    times = pd.DatetimeIndex(wse.coords["time"].values)
    values = np.asarray(wse.values)
    finite = np.isfinite(values)
    if len(times) < 2 or not times.is_monotonic_increasing or times.has_duplicates:
        raise RuntimeError(f"Invalid result timestamps for {mesh_name}")
    if values.shape != (len(times), cell_count) or not finite.all():
        raise RuntimeError(f"Missing or inconsistent water-surface data for {mesh_name}")
    if times[0] != expected_start or times[-1] != expected_end:
        raise RuntimeError(f"Result timestamps do not cover the requested plan window: {mesh_name}")
    water_surfaces[mesh_name] = wse
    rows.append({
        "mesh": mesh_name, "reported_cells": prepared_counts[mesh_name],
        "water_surface_columns": cell_count, "timesteps": len(times),
        "first_output": times[0], "last_output": times[-1],
        "finite_fraction": float(finite.mean()),
        "finite_wse_min": float(values[finite].min()),
        "finite_wse_max": float(values[finite].max()),
    })

display(pd.DataFrame(rows))
print(f"Requested plan window: {expected_start} to {expected_end}")
print(f"Final result: {result_hdf}")

Plot one cell's water-surface time series

This diagnostic plot chooses the cell with the largest water-surface range among cells represented in the first mesh's hydraulic property table. Select the cell and mesh appropriate to your review before drawing hydraulic conclusions. The vertical units come from the result HDF.

Python
mesh_name = next(iter(water_surfaces))
wse = water_surfaces[mesh_name]
physical_ids = cell_tables[mesh_name]["Cell ID"].unique().astype(int)
ranges = np.ptp(wse.values[:, physical_ids], axis=0)
cell_id = int(physical_ids[np.argmax(ranges)])
series = wse.isel(cell_id=cell_id)
fig, ax = plt.subplots(figsize=(10, 3.5))
ax.plot(pd.to_datetime(series.time.values), series.values)
ax.set(title=f"{mesh_name}: cell {cell_id}", xlabel="Simulation time",
       ylabel=f"Water-surface elevation ({wse.attrs.get('units', 'model units')})")
ax.grid(alpha=0.25)
fig.autofmt_xdate()
plt.show()

Optional host batch summary

RasDocker.run_batch() processes the supplied jobs sequentially on the host, with one selected plan per container invocation. It returns ContainerBatchResult.summary_df, including failed jobs, stage receipts, elapsed time, and whether completed work was reused. A failed job remains in the summary and later jobs can continue; there is no worker pool inside the container.

The cell is disabled by default and launches no additional calculations unless explicitly enabled. Its one-job example uses the project already prepared above and illustrates stage="compute". Enable resume before the original stage calls to reuse that successful compute here. Add other independently prepared working copies to jobs when applying the pattern to a batch; do not list the same active working folder concurrently in an external scheduler.

Use stage="prepare" for preprocessing only or stage="run" for preparation followed by computation. Shared options are passed as keywords, and each job can override them. An external scheduler can invoke the single-plan APIs on separate working folders when concurrency is needed.

Python
RUN_OPTIONAL_BATCH = False

if RUN_OPTIONAL_BATCH:
    if not RESUME_COMPLETED_STAGES:
        raise ValueError("Enable RESUME_COMPLETED_STAGES before the original phase calls to reuse this completed job.")
    jobs = [{
        "project_path": project,
        "plan_number": PLAN,
        "prepare_receipt": preparation.receipt_path,
    }]
    batch = RasDocker.run_batch(
        jobs,
        stage="compute",
        version=VERSION,
        image=COMPUTE_IMAGE,
        timeout=COMPUTE_TIMEOUT,
        num_cores=NUM_CORES,
        pull=PULL_POLICY,
        stream_callback=progress_callback,
        resume=True,
    )
    display(batch.summary_df)
    print(f"All submitted jobs succeeded: {batch.success}")

Retain the reviewable run

Keep the complete working model, its original preparation .tmp.hdf, the final .p01.hdf, and both .ras-commander/runs/<run-id>/ receipts. If a call fails, share the corresponding receipt and logs together with the selected version and the dependency layout.

The container execution guide explains the mounts and internal operation. The native container README lists installed source files and build inputs. See 510_linux_execution.ipynb for the underlying native-solver workflow and 511_headless_linux_wine_ras2cng.ipynb for broader Wine/runtime qualification.

If you share results or publish work using the library, consider citing ras-commander and its contributors.

For the 7.0.1 build inputs, see the source of extract_installer.py.