8.3.8. Hydrology Module

8.3.8.1. Introduction

The Python PFTools Hydrology module provides standalone functions for common hydrologic calculations.

8.3.8.2. Usage of Hydrology

First, we’ll show some examples of using the Hydrology class within a ParFlow Python script:

import numpy as np
from parflow import Run
from parflow.tools.hydrology import calculate_surface_storage, calculate_subsurface_storage, \
    calculate_water_table_depth, calculate_evapotranspiration, calculate_overland_flow_grid

# Create a Run object from the .pfidb file
run = Run.from_definition('/path/to/pfidb/file')

# Get the DataAccessor object corresponding to the Run object
data = run.data_accessor

# ----------------------------------------------
# Get relevant information from the DataAccessor
# ----------------------------------------------

# Resolution
dx = data.dx
dy = data.dy
# Thickness of each layer, bottom to top
dz = data.dz

# Extent
nx = data.shape[2]
ny = data.shape[1]
nz = data.shape[0]

# ------------------------------------------
# Time-invariant values
# ------------------------------------------

porosity = data.computed_porosity
specific_storage = data.specific_storage
mask = data.mask
et = data.et                        # shape (nz, ny, nx) - units 1/T.
slopex = data.slope_x               # shape (ny, nx)
slopey = data.slope_y               # shape (ny, nx)
mannings = data.mannings            # scalar value

# ------------------------------------------
# Time-variant values
# ------------------------------------------

# no. of time steps
nt = len(data.times)

# ------------------------------------------
# Initialization
# ------------------------------------------

# Arrays for total values (across all layers), with time as the first axis
subsurface_storage = np.zeros(nt)
surface_storage = np.zeros(nt)
wtd = np.zeros((nt, ny, nx))
et = np.zeros(nt)
overland_flow = np.zeros((nt, ny, nx))

# ------------------------------------------
# Loop through time steps
# i goes from 0 to n_timesteps - 1
# ------------------------------------------
for i in data.times:
    pressure = data.pressure
    saturation = data.saturation

    # Total subsurface storage for this time step is the summation of substorage surface across all x/y/z slices
    subsurface_storage[i, ...] = np.sum(
        calculate_subsurface_storage(porosity, pressure, saturation, specific_storage, dx, dy, dz, mask=mask),
        axis=(0, 1, 2)
    )

    # Total surface storage for this time step is the summation of substorage surface across all x/y slices
    surface_storage[i, ...] = np.sum(
        calculate_surface_storage(pressure, dx, dy, mask=mask),
        axis=(0, 1)
    )

    wtd[i, ...] = calculate_water_table_depth(pressure, saturation, dz)

    if et is not None:
        # Total ET for this time step is the summation of ET values across all x/y/z slices
        et[i, ...] = np.sum(
            calculate_evapotranspiration(et_flux_values, dx, dy, dz, mask=mask),
            axis=(0, 1, 2)
        )

    overland_flow[i, ...] = calculate_overland_flow_grid(pressure, slopex, slopey, mannings, dx, dy, mask=mask)

    data.time += 1

8.3.8.3. Full API

  1. calculate_water_table_depth(pressure, saturation, dz)

    Calculate water table depth from the land surface.

    param pressure:

    An nz by ny by nx ndarray of pressure values (bottom layer to top layer)

    param saturation:

    An nz by ny by nx ndarray ndarray of saturation values (bottom layer to top layer)

    param dz:

    An ndarray of shape (nz,) of thickness values (bottom layer to top layer)

    return:

    A ny by nx ndarray of water table depth values (measured from the top)

  2. calculate_subsurface_storage(porosity, pressure, saturation, specific_storage, dx, dy, dz, mask=None)

    Calculate gridded subsurface storage across several layers. For each layer in the subsurface, storage consists of two parts:

    1. Incompressible subsurface storage (porosity * saturation * depth of this layer) * dx * dy

    2. Compressible subsurface storage (pressure * saturation * specific storage * depth of this layer) * dx * dy

    param porosity:

    An nz by ny by nx ndarray of porosity values (bottom layer to top layer)

    param pressure:

    An nz by ny by nx ndarray of pressure values (bottom layer to top layer)

    param saturation:

    An nz by ny by nx ndarray of saturation values (bottom layer to top layer)

    param specific_storage:

    An nz by ny by nx ndarray of specific storage values (bottom layer to top layer)

    param dx:

    Length of a grid element in the x direction

    param dy:

    Length of a grid element in the y direction

    param dz:

    Thickness of a grid element in the z direction (bottom layer to top layer)

    param mask:

    An nz by ny by nx ndarray of mask values (bottom layer to top layer). If None, assumed to be an nz by ny by nx ndarray of 1s.

    return:

    An nz by ny by nx ndarray of subsurface storage values, spanning all layers (bottom to top)

  3. calculate_surface_storage(pressure, dx, dy, mask=None)

    Calculate gridded surface storage on the top layer. Surface storage is given by: Pressure at the top layer * dx * dy (for pressure values > 0)

    param pressure:

    An nz by ny by nx ndarray of pressure values (bottom layer to top layer)

    param dx:

    Length of a grid element in the x direction

    param dy:

    Length of a grid element in the y direction

    param mask:

    An nz by ny by nx ndarray of mask values (bottom layer to top layer). If None, assumed to be an nz by ny by nx ndarray of 1s.

    return:

    An ny by nx ndarray of surface storage values

  4. calculate_evapotranspiration(et, dx, dy, dz, mask=None)

    Calculate gridded evapotranspiration across several layers.

    param et:

    An nz by ny by nx ndarray of evapotranspiration flux values with units 1/T (bottom layer to top layer)

    param dx:

    Length of a grid element in the x direction

    param dy:

    Length of a grid element in the y direction

    param dz:

    Thickness of a grid element in the z direction (bottom layer to top layer)

    param mask:

    An nz by ny by nx ndarray of mask values (bottom layer to top layer). If None, assumed to be an nz by ny by nx ndarray of 1s.

    return:

    An nz by ny by nx ndarray of evapotranspiration values (units L^3/T), spanning all layers (bottom to top)

  5. calculate_overland_fluxes(pressure, slopex, slopey, mannings, dx, dy, flow_method='OverlandKinematic', epsilon=1e-5, mask=None)

    Calculate overland fluxes across grid faces.

    param pressure:

    An nz by ny by nx ndarray of pressure values (bottom layer to top layer)

    param slopex:

    ny by nx

    param slopey:

    ny by nx

    param mannings:

    a scalar value, or a ny by nx ndarray

    param dx:

    Length of a grid element in the x direction

    param dy:

    Length of a grid element in the y direction

    param flow_method:

    Either ‘OverlandFlow’ or ‘OverlandKinematic’. ‘OverlandKinematic’ by default.

    param epsilon:

    Minimum slope magnitude for solver. Only applicable if flow_method='OverlandKinematic'. This is set using the Solver.OverlandKinematic.Epsilon key in Parflow.

    param mask:

    An nz by ny by nx ndarray of mask values (bottom layer to top layer). If None, assumed to be an nz by ny by nx ndarray of 1s.

    return:

    A 2-tuple:

    (qeast: A ny by (nx+1) ndarray of overland flux values,

    qnorth: A (ny+1) by nx ndarray of overland flux values)

    Numpy array origin is at the top left.
    The cardinal direction along axis 0 (rows) is North (going down!!).
    The cardinal direction along axis 1 (columns) is East (going right).
    qnorth ``(ny+1,nx)`` and qeast ``(ny,nx+1)`` values are to be interpreted as follows.
    
    +-------------------------------------> (East)
    |
    |                           qnorth_i,j (outflow if negative)
    |                                  +-----+------+
    |                                  |     |      |
    |                                  |     |      |
    |  qeast_i,j (outflow if negative) |-->  v      |---> qeast_i,j+1 (outflow if positive)
    |                                  |            |
    |                                  | Cell  i,j  |
    |                                  +-----+------+
    |                                        |
    |                                        |
    |                                        v
    |                           qnorth_i+1,j (outflow if positive)
    v
    (North)
    
  6. calculate_overland_flow_grid(pressure, slopex, slopey, mannings, dx, dy, flow_method='OverlandKinematic', epsilon=1e-5, mask=None)

    Calculate overland outflow per grid cell of a domain.

    param pressure:

    An nz by ny by nx ndarray of pressure values (bottom layer to top layer)

    param slopex:

    ny by nx

    param slopey:

    ny by nx

    param mannings:

    a scalar value, or a ny by nx ndarray

    param dx:

    Length of a grid element in the x direction

    param dy:

    Length of a grid element in the y direction

    param flow_method:

    Either ‘OverlandFlow’ or ‘OverlandKinematic’. ‘OverlandKinematic’ by default.

    param epsilon:

    Minimum slope magnitude for solver. Only applicable if kinematic=True. This is set using the Solver.OverlandKinematic.Epsilon key in Parflow.

    param mask:

    An nz by ny by nx ndarray of mask values (bottom layer to top layer). If None, assumed to be an nz by ny by nx ndarray of 1s.

    return:

    An ny by nx ndarray of overland flow values

  7. calculate_overland_flow(pressure, slopex, slopey, mannings, dx, dy, flow_method='OverlandKinematic', epsilon=1e-5, mask=None)

    param pressure:

    An nz by ny by nx ndarray of pressure values (bottom layer to top layer)

    param slopex:

    ny by nx

    param slopey:

    ny by nx

    param mannings:

    a scalar value, or a ny by nx ndarray

    param dx:

    Length of a grid element in the x direction

    param dy:

    Length of a grid element in the y direction

    param flow_method:

    Either ‘OverlandFlow’ or ‘OverlandKinematic’. ‘OverlandKinematic’ by default.

    param epsilon:

    Minimum slope magnitude for solver. Only applicable if flow_method='OverlandKinematic'. This is set using the Solver.OverlandKinematic.Epsilon key in Parflow.

    param mask:

    An nz by ny by nx ndarray of mask values (bottom layer to top layer). If None, assumed to be an nz by ny by nx ndarray of 1s.

    return:

    A ny by nx ndarray of overland flow values