Wolfresults_2D & wolfres2DGPU — 2D simulation results

  • ``Wolfresults_2D`` — base class for WOLF2D results (CPU multi-block)

  • ``wolfres2DGPU`` — GPU-specific subclass (single-block, ResultsStore-backed)

  • ``OneWolfResult`` — per-block container (waterdepth, qx, qy, topography, …)

  • ``views_2D`` — enum of available result views

Architecture

Wolfresults_2D  (or wolfres2DGPU)
├── myblocks: dict[str, OneWolfResult]
│   └── 'block1' -> OneWolfResult
│       ├── .waterdepth   (WolfArray)
│       ├── .qx           (WolfArray)  — discharge X [m²/s]
│       ├── .qy           (WolfArray)  — discharge Y [m²/s]
│       ├── .top          (WolfArray)  — topography
│       └── .rough_n      (WolfArray)  — Manning coefficient
├── head_blocks: dict[str, header_wolf]
├── times: list[float]       — real times [s]
├── current_result: int      — currently loaded step (0-based)
├── epsilon: float           — wet/dry threshold
└── mypal: wolfpalette       — color palette
[1]:
from wolfhece.wolfresults_2D import Wolfresults_2D, views_2D, OneWolfResult
from wolfhece.Results2DGPU import wolfres2DGPU
from wolfhece.wolf_array import WolfArray, getkeyblock

views_2D — available result views

The views_2D enum defines all possible computed views. The most commonly used are shown below.

[2]:
# List all views
for v in views_2D:
    print(f"{v.name:40s}  {v.value}")
WATERDEPTH                                Water depth [m]
WATERLEVEL                                Water level [m]
TOPOGRAPHY                                Bottom level [m]
QX                                        Discharge X [m2s-1]
QY                                        Discharge Y [m2s-1]
QNORM                                     Discharge norm [m2s-1]
UX                                        Velocity X [ms-1]
UY                                        Velocity Y [ms-1]
UNORM                                     Velocity norm [ms-1]
HEAD                                      Head [m]
FROUDE                                    Froude [-]
KINETIC_ENERGY                            Kinetic energy k
EPSILON                                   Rate of dissipation e
TURB_VISC_2D                              Turbulent viscosity 2D
TURB_VISC_3D                              Turbulent viscosity 3D
VECTOR_FIELD_Q                            Discharge vector field
VECTOR_FIELD_U                            Velocity vector field
U_SHEAR                                   Shear velocity [ms-1]
SHIELDS_NUMBER                            Shields number - Manning-Strickler
CRITICAL_DIAMETER_SHIELDS                 Critical grain diameter - Shields
CRITICAL_DIAMETER_IZBACH                  Critical grain diameter - Izbach
CRITICAL_DIAMETER_SUSPENSION_50           Critical grain diameter - Suspension 50%
CRITICAL_DIAMETER_SUSPENSION_100          Critical grain diameter - Suspension 100%
QNORM_FIELD                               Q norm + Field
UNORM_FIELD                               U norm + Field
WL_Q                                      WL + Q
WD_Q                                      WD + Q
WL_U                                      WL + U
WD_U                                      WD + U
T_WL_Q                                    Top + WL + Q
T_WD_Q                                    Top + WD + Q
T_WD_U                                    Top + WD + U

getkeyblock — block key convention

getkeyblock(i, addone=True) converts a 0-based Python index to a block key string:

  • getkeyblock(0)'block1'

  • getkeyblock(1)'block2'

With addone=False, the number is used as-is:

  • getkeyblock(1, False)'block1'

[3]:
print(f"getkeyblock(0)        = '{getkeyblock(0)}'")
print(f"getkeyblock(1)        = '{getkeyblock(1)}'")
print(f"getkeyblock(1, False) = '{getkeyblock(1, False)}'")
getkeyblock(0)        = 'block1'
getkeyblock(1)        = 'block2'
getkeyblock(1, False) = 'block1'

Loading GPU results with wolfres2DGPU

wolfres2DGPU can load results from:

  • A local directory containing parameters.json and simul_gpu_results/

  • An HTTP(S) URL (triggers automatic download via download_gpu_simulation)

  • A pre-built ``ResultsStore`` (via the store parameter)

GPU simulations are always single-block ('block1').

[4]:
from wolfhece.pydownloader import toys_gpu_dataset, DATADIR

# Download a toy GPU simulation (cached)
store = toys_gpu_dataset('channel_w_archbridge_fully_man004')
print(f"ResultsStore: {store.nb_results} saved time steps")

# Load into wolfres2DGPU
sim_path = DATADIR / 'channel_w_archbridge_fully_man004'
sim = wolfres2DGPU(str(sim_path), eps=0.01)

print(f"\nBlocks: {list(sim.myblocks.keys())}")
print(f"Epsilon (wet/dry threshold): {sim.epsilon}")
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\NAP.npy already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\bathymetry.npy already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\bridge_roof.npy already exists. Skipping download.
ERROR:root:HTTP error occurred while downloading https://gitlab.uliege.be/HECE/wolfgpu_examples/-/raw/main/channel_w_archbridge_fully_man004/bridge_deck.npy: 404 Client Error: Not Found for url: https://gitlab.uliege.be/HECE/wolfgpu_examples/-/raw/main/channel_w_archbridge_fully_man004/bridge_deck.npy
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\h.npy already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\manning.npy already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\qx.npy already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\qy.npy already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\parameters.json already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\metadata.json already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\nap.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\nb_results.txt already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\sim_times.csv already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\h_0000001.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\qx_0000001.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\qy_0000001.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\h_0000002.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\qx_0000002.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\qy_0000002.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\h_0000003.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\qx_0000003.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\qy_0000003.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\h_0000004.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\qx_0000004.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\qy_0000004.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\h_0000005.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\qx_0000005.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\qy_0000005.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\h_0000006.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\qx_0000006.npz already exists. Skipping download.
INFO:root:File C:\Users\pierre\Documents\Gitlab\HECEPython\wolfhece\data\downloads\channel_w_archbridge_fully_man004\simul_gpu_results\qy_0000006.npz already exists. Skipping download.
ResultsStore: 6 saved time steps

Blocks: ['block1']
Epsilon (wet/dry threshold): 0.01

Querying time steps

[5]:
nb = sim.get_nbresults()
print(f"Number of saved time steps: {nb}")

# Get all real times and calculation step numbers
times = sim.times
print(f"\nFirst 5 times [s]: {times[:5]}")
print(f"Last time [s]:      {times[-1]}")
Number of saved time steps: 6

First 5 times [s]: [0.0, 40.60536419227719, 81.21073305234313, 121.81609793752432, 162.4214591383934]
Last time [s]:      203.02682216465473

Reading a single time step

read_oneresult(which) loads a specific saved step (0-based) into the myblocks arrays. Pass -1 for the last step.

[6]:
# Read the last time step
sim.read_oneresult(-1)
print(f"Loaded step: {sim.current_result}")

# Access the block
block = sim.myblocks[getkeyblock(1, False)]

# Water depth statistics
h = block.waterdepth
print(f"\nWater depth array: {h.nbx} x {h.nby}")
print(f"  max h = {h.array.max():.3f} m")
print(f"  wet cells = {h.nbnotnull}")

# Discharges
print(f"\nMax |qx| = {abs(block.qx.array).max():.3f} m\u00b2/s")
print(f"Max |qy| = {abs(block.qy.array).max():.3f} m\u00b2/s")
Loaded step: 5

Water depth array: 500 x 21
  max h = 8.042 m
  wet cells = 8466

Max |qx| = 6.845 m²/s
Max |qy| = 2.085 m²/s

Switching views

set_currentview(views_2D.XXX) recomputes the active display array (e.g. velocity from h and q). The result is then accessible via as_WolfArray().

[7]:
# Switch to velocity norm
sim.set_currentview(views_2D.UNORM)
v_array = sim.as_WolfArray(copyarray=True)
print(f"Velocity norm: max = {v_array.array.max():.3f} m/s")

# Switch to water level (h + topography)
sim.set_currentview(views_2D.WATERLEVEL)
wl_array = sim.as_WolfArray(copyarray=True)
print(f"Water level: max = {wl_array.array.max():.3f} m")

# Switch back to water depth
sim.set_currentview(views_2D.WATERDEPTH)
Velocity norm: max = 0.856 m/s
Water level: max = 8.042 m

Exporting as WolfArray

as_WolfArray(copyarray=True) creates a standalone WolfArray from the current view. This is useful for saving, plotting, or further processing.

[8]:
# Export current view as a WolfArray
wa = sim.as_WolfArray(copyarray=True)
print(f"Exported WolfArray: {wa.nbx} x {wa.nby}")
print(f"dx={wa.dx}, dy={wa.dy}")
print(f"Origin: ({wa.origx:.0f}, {wa.origy:.0f})")

# Save to disk
import tempfile
from pathlib import Path
tmpdir = Path(tempfile.mkdtemp())
wa.write_all(str(tmpdir / 'waterdepth_last.bin'))
print(f"\nSaved to: {tmpdir / 'waterdepth_last.bin'}")
Exported WolfArray: 500 x 21
dx=1.0, dy=1.0
Origin: (0, 0)

Saved to: C:\Users\pierre\AppData\Local\Temp\tmptth17b7q\waterdepth_last.bin

Topography access

[9]:
# Topography for the block
top = sim.get_top_for_block(getkeyblock(1, False))
print(f"Topography: min = {top.array.min():.2f} m, max = {top.array.max():.2f} m")
Topography: min = 0.00 m, max = 100.00 m

Epsilon — wet/dry threshold

The epsilon attribute controls which cells are considered “wet”:

  • epsilon > 0: cells with h < epsilon are masked

  • epsilon == 0: only cells with h == 0 are masked

Changing epsilon and re-reading updates the mask.

[10]:
# Default epsilon
print(f"Current epsilon: {sim.epsilon}")
sim.read_oneresult(-1)
wet_default = sim.myblocks[getkeyblock(1, False)].waterdepth.nbnotnull
print(f"Wet cells (eps={sim.epsilon}): {wet_default}")

# Increase epsilon to filter thin water layers
sim.epsilon = 0.1
sim.read_oneresult(-1)
wet_filtered = sim.myblocks[getkeyblock(1, False)].waterdepth.nbnotnull
print(f"Wet cells (eps={sim.epsilon}): {wet_filtered}")

# Reset
sim.epsilon = 0.01
Current epsilon: 0.01
Wet cells (eps=0.01): 8466
Wet cells (eps=0.1): 8466

Iterating over time steps

[11]:
# Read every 10th step and track maximum water depth
import numpy as np

nb = sim.get_nbresults()
step_indices = range(0, nb, max(1, nb // 10))  # ~10 samples
max_depths = []

for i in step_indices:
    sim.read_oneresult(i)
    h_max = sim.myblocks[getkeyblock(1, False)].waterdepth.array.max()
    max_depths.append((sim.times[i], float(h_max)))

print(f"{'Time [s]':>10s}  {'Max h [m]':>10s}")
print('-' * 22)
for t, hm in max_depths:
    print(f"{t:10.1f}  {hm:10.3f}")
  Time [s]   Max h [m]
----------------------
       0.0       8.042
      40.6       8.042
      81.2       8.042
     121.8       8.042
     162.4       8.042
     203.0       8.042

In-memory cache (GPU only)

setup_cache() preloads a range of results into RAM for faster repeated access. Use only_h=True to load only water depth and save memory.

[12]:
# Cache the first 20 steps
sim.setup_cache(start_idx=0, end_idx=20, only_h=True)

# Reads from cache (fast)
sim.read_oneresult(5)
print(f"Step 5 from cache: max h = {sim.myblocks[getkeyblock(1, False)].waterdepth.array.max():.3f} m")

# Release cache
sim.clear_cache()
print("Cache cleared")
INFO:root:Estimated memory size for one result: 0.07 MB
Step 5 from cache: max h = 8.042 m
Cache cleared

Spatial sub-region reading (GPU only)

read_oneresult_subarray() loads only a spatial sub-region, which is faster when the full domain is large and you only need a local area.

Bounds can be:

  • [[xmin, xmax], [ymin, ymax]] — list of lists

  • A vector object (uses its bounding box)

  • A tuple ((xmin, xmax), (ymin, ymax))

Important: the actual loaded region may differ slightly from the requested bounds. A safety border (default max(dx, dy)) is added, and world coordinates are converted to cell indices via floor(), so the loaded footprint is always aligned to the grid and can be a few cells larger than requested. All cells outside the sub-region are set to the null value (or left untouched if nullify_all_outside=False).

[ ]:
# Read only a sub-region
block = sim.myblocks[getkeyblock(1, False)]
bounds = block.waterdepth.get_bounds()
xmid = (bounds[0][0] + bounds[0][1]) / 2
ymid = (bounds[1][0] + bounds[1][1]) / 2

# Define a small window around the center.
# Note: the actually loaded region may be slightly larger than the requested
# bounds because coordinates are converted to cell indices via floor(), and a
# safety border (default = max(dx, dy)) is added on each side.
# The data outside the loaded sub-region is set to the null value.
sub_bounds = [[xmid - 50, xmid + 50],
              [ymid - 50, ymid + 50]]

sim.read_oneresult_subarray(which=-1, bounds=sub_bounds)
wet = sim.myblocks[getkeyblock(1, False)].waterdepth.nbnotnull
print(f"Wet cells in sub-region: {wet}")
Wet cells in sub-region: 412

Danger maps

Danger maps iterate over all (or a subset of) time steps and compute maximum envelopes and temporal indicators.

Returns a tuple of 9 WolfArray:

Index

Name

Description

0

H

Maximum water depth [m]

1

U

Maximum velocity norm [m/s]

2

Q

Maximum momentum norm [m:nbsphinx-math:`u0`0b2/s]

3

Z

Maximum water level [m]

4

Head

Maximum total head [m]

5

ToA

Time of arrival [s]

6

ToM

Time of maximum [s]

7

DoI

Duration of inundation [s]

8

ToE

Time of ending [s]

[14]:
# Compute danger maps (using every 5th step for speed)
H, U, Q, Z, Head, ToA, ToM, DoI, ToE = sim.danger_map(
    start=0, end=-1, every=5, hmin=0.01
)

print(f"Max water depth:  {H.array.max():.3f} m")
print(f"Max velocity:     {U.array.max():.3f} m/s")
print(f"Max momentum:     {Q.array.max():.3f} m\u00b2/s")
print(f"Max water level:  {Z.array.max():.3f} m")
print(f"Time of arrival:  min = {ToA.array.min():.0f} s (excluding dry)")
100%|██████████| 2/2 [00:00<00:00, 116.29it/s]
Max water depth:  8.042 m
Max velocity:     0.856 m/s
Max momentum:     6.846 m²/s
Max water level:  8.042 m
Time of arrival:  min = 203 s (excluding dry)

GPU-optimized tiled danger maps

danger_map_gpu_tiled() operates directly on tiled GPU arrays without per-step untiling, which is significantly faster for large domains. Same return format.

[15]:
# Tiled version (GPU only, faster)
try:
    H2, U2, Q2, Z2, Head2, ToA2, ToM2, DoI2, ToE2 = sim.danger_map_gpu_tiled(
        start=0, end=-1, every=5, hmin=0.01
    )
    if H2 is not None:
        print(f"Tiled max water depth: {H2.array.max():.3f} m")
    else:
        print("Tile packer not available (requires wolfgpu >= 1.4.0)")
except Exception as e:
    print(f"Tiled danger map not available: {e}")
100%|██████████| 2/2 [00:00<00:00, 420.52it/s]
Tiled max water depth: 8.042 m

Loading CPU results

CPU simulations use Wolfresults_2D directly and may have multiple blocks (multi-domain models).

from wolfhece.wolfresults_2D import Wolfresults_2D

res = Wolfresults_2D(fname='path/to/simul')

nb = res.get_nbresults()
res.read_oneresult(10)  # 0-based

# Access blocks
for key, block in res.myblocks.items():
    print(f"{key}: {block.waterdepth.nbx} x {block.waterdepth.nby}")

# Multi-block export
wa_mb = res.as_WolfArray(copyarray=True, force_mb=True)  # WolfArrayMB

Loading from a URL (GPU)

wolfres2DGPU can download a simulation directly from a URL:

sim = wolfres2DGPU('https://gitlab.uliege.be/.../my_simulation/')

This calls download_gpu_simulation internally, downloads all input maps and result files to DATADIR, and loads the simulation.

GPU metadata properties

[16]:
# Access time series metadata from ResultsStore
try:
    dt_series = sim.all_dt
    print(f"Time step series length: {len(dt_series)}")
    print(f"First few dt values: {dt_series[:5]}")
except Exception as e:
    print(f"Metadata not available: {e}")
Time step series length: 6
First few dt values: [0.0, 1000.0, 2000.0, 3000.0, 4000.0]

Independent zone filtering

When to_filter_independent = True, each read_oneresult call automatically keeps only the largest connected wet zone(s), removing isolated puddles.

[17]:
# Enable independent zone filtering
sim.to_filter_independent = True
sim.read_oneresult(-1)
wet_filtered = sim.myblocks[getkeyblock(1, False)].waterdepth.nbnotnull

sim.to_filter_independent = False
sim.read_oneresult(-1)
wet_all = sim.myblocks[getkeyblock(1, False)].waterdepth.nbnotnull

print(f"Wet cells without filtering: {wet_all}")
print(f"Wet cells with filtering:    {wet_filtered}")
Wet cells without filtering: 8466
Wet cells with filtering:    8466

Summary

Task

Code

Load GPU results

wolfres2DGPU(path, eps=0.01)

Load CPU results

Wolfresults_2D(fname=path)

Number of steps

sim.get_nbresults()

Read a step

sim.read_oneresult(i) (0-based, -1=last)

Get times

sim.times

Switch view

sim.set_currentview(views_2D.UNORM)

Export to WolfArray

sim.as_WolfArray(copyarray=True)

Get topography

sim.get_top_for_block(getkeyblock(1, False))

Set threshold

sim.epsilon = 0.01

Sub-region read

sim.read_oneresult_subarray(which, bounds)

Danger map

sim.danger_map(start, end, every, hmin)

Tiled danger map

sim.danger_map_gpu_tiled(start, end, every, hmin)

Cache results

sim.setup_cache(start, end, only_h)

Filter islands

sim.to_filter_independent = True

See also: