Skip to contents

Runs LANDIS-II in an ephemeral Docker container, blocking until the simulation completes. The scenario directory is bind-mounted to /sim inside the container. Stdout and stderr are written to <scenario_dir>/log/docker_stdout.log and docker_stderr.log. Wall-clock elapsed time and peak memory use (polled every 2 s from docker stats) are reported on completion and written to <scenario_dir>/log/docker_resources.log. The image's immutable sha256 digest is captured into <scenario_dir>/log/docker_image.log so downstream provenance tools can pin runs to a specific image regardless of subsequent tag movement.

Usage

landis_run_docker(
  scenario_dir,
  scenario_file = "scenario.txt",
  image = NULL,
  console = NULL,
  pull = FALSE,
  cpu_limit = 2,
  mem_limit = "8g",
  mem_margin = 1.5,
  post_completion_timeout_sec = 300,
  startup_jitter = NULL,
  stream_to = NULL,
  stream_every_sec = 600,
  stream_lag_steps = 2L,
  stream_jitter_frac = 0.25,
  stream_patterns = .default_stream_patterns()
)

Arguments

scenario_dir

Character. Path to the scenario directory (resolved to absolute before mounting).

scenario_file

Character. Scenario filename relative to scenario_dir.

image

Character or NULL. Docker image reference. Defaults to getOption("landisutils.docker.image") (set by .onLoad() to "ghcr.io/landis-ii-foundation/landis-ii-v8-release:main").

console

Character or NULL. Path to Landis.Console.dll inside the container. Defaults to NULL, which calls landis_find_docker() at run time (reads getOption("landisutils.docker.console")).

pull

Logical. When TRUE, run docker pull <image> before the simulation so the recorded digest reflects the current registry rather than a stale local copy. Defaults to FALSE to keep runs reproducible across iterations of an already-cached image.

cpu_limit

Numeric or NULL. Hard CPU cap for the container (docker run --cpus). Default 2: LANDIS-II compute is effectively single-threaded – empirical measurement across 90 concurrent ForCS + Dynamic Fire + Dynamic Fuels containers shows a median of 1.00 cores used per container, p99 = 1.11 cores, max = 1.11 cores. The .NET runtime hosts 9-11 OS threads but only the simulator thread is compute-bound; the GC / threadpool helpers occasionally peek above 1.0 cores in brief bursts. 2 covers that 99th-percentile burst with ~80% headroom; 1 is tight enough that the .NET GC helper would contend with the simulator thread for cycles. Pass NULL for no CPU limit.

mem_limit

Numeric byte count, character (e.g. "8g", "512m"), NULL, or Inf. Baseline RAM cap (docker run --memory). Default "8g". When a prior <rep_dir>/log/*_resources.log exists with a recorded peak_mem_bytes, the cap is raised to peak_mem_bytes * mem_margin if that exceeds the baseline – a rep that fit last time will never be killed by this cap on a rerun. When no prior log exists for the rep (first run, or rep dir freshly deleted), the cap is dropped entirely so the first run can discover what it needs.

mem_margin

Numeric. Headroom factor applied to a previously observed peak when auto-raising mem_limit. Default 1.5.

post_completion_timeout_sec

Numeric. Grace period (seconds) after the string "Model run is complete." first appears in the container's stdout before the watchdog SIGTERMs the container. Some long ForCS + Dynamic Fire scenarios with many output extensions log this completion marker but then spin in the .NET runtime shutdown path indefinitely (outputs are already on disk, so the sim itself completed cleanly). On timeout the container is stopped and exit codes 137/143 are remapped to 0. Set to Inf to disable the watchdog. Default 300 (5 min).

startup_jitter

Numeric seconds or NULL. Upper bound on a random start delay applied before the function touches Docker, to stagger container launches and avoid a thundering-herd surge on the Docker daemon (and on the disk backing the image layers / renv library) when many replicates start at once under crew. Each call sleeps runif(1, 0, startup_jitter) seconds. The delay cannot affect simulation results (identical seed and inputs), so when run from tar_landis() it is deliberately not baked into the {targets} command – changing it never invalidates completed replicates. NULL (the default) reads the LANDIS_STARTUP_JITTER environment variable (so it can be set once in a project .Rprofile that crew workers inherit); an unset/invalid value means 0 (no stagger).

stream_to

Character or NULL. When set, completed output maps are moved here while the simulation runs, so local scratch holds only the working set instead of the whole replicate. Nothing is discarded – files are relocated, not pruned. Point this at the <final>.partial staging directory used by landis_archive_rep() so the atomic-rename publish, and therefore the all-or-nothing appearance of the final directory, is preserved. NULL (default) disables streaming.

stream_every_sec

Numeric. Seconds between sync attempts (default 600). Actual intervals are jittered; see stream_jitter_frac.

stream_lag_steps

Integer. How many timesteps behind the simulation to stay (default 2). LANDIS-II writes several rasters per timestep, so a file for step t can still be open when the log already reports Current time: t; the lag is what makes "written" mean "closed".

stream_jitter_frac

Numeric in [0, 1]. Fraction by which each interval is randomly stretched or shrunk (default 0.25). Replicates launched together would otherwise sync in lockstep for the whole run, turning a steady trickle into periodic bursts against one shared filesystem.

stream_patterns

Character vector of regexes matching output maps that are safe to move mid-run. Defaults to write-once fire and fuels maps; deliberately excludes TimeOfLastFire-*, which may be simulation state.

Value

Named list with exit_code (integer), elapsed_sec (numeric), and peak_mem_bytes (numeric), returned invisibly.

Details

Single container per replicate. The container name is derived deterministically from the (real) scenario directory, so docker's own name uniqueness acts as a cross-worker mutex: if the same replicate is dispatched to two workers at once (for example when targets re-runs a branch after a false-positive worker crash while the original container is still running), the second call cannot start a parallel container and instead adopts the running one: it waits for that container to finish (applying the same post-completion watchdog) and returns its result. A duplicate dispatch thus becomes a harmless wait rather than two dotnet processes truncating each other's outputs.