MILC

Implementation

LuMod single-layer MISDc/MILC; upstream routing dt=0.2

  • Time step: daily

  • Backend: installed Python dependencies

  • Calibrated parameters: 6

  • Temperature required: no

Inspect the exact implementation.

Parameters and initial configuration

Parameter

Supported calibration range

Default

gamma

0.5 to 15

5

w0

Fixed initial/configuration value

0.5

wmax

50 to 2000

1000

m

1 to 20

10

ks

1 to 300

100

kc

Fixed initial/configuration value

1

nu

0.01 to 0.99

0.5

alpha

0.1 to 5

2

Ranges/defaults are implementation contracts, not universal priors or a recommended basin calibration. Consult source comments for parameter units and coupling.

Governing equations

The following source is the exact model kernel used by this adapter. For MARRMoT it includes the state derivative and each referenced flux function; the solver and routing are described above. Original notices and source citations are retained in the files.

# -*- coding: utf-8 -*-
"""
Modello Idrologico Lumped in Continuo (MILC)

Rain-Runoff Model for daily simulation


Author:
Saul Arciniega Esparza
Hydrogeology Group, Faculty of Engineering,
National Autonomous University of Mexico
zaul.ae@gmail.com | sarciniegae@comunidad.unam.mx


Based on:
MISDc Rainfall-Runoff Model (https://github.com/IRPIhydrology/MISDc)

Reference:
Brocca, L., Liersch, S., Melone, F., Moramarco, T., Volk, M. (2013).
Application of a model-based rainfall-runoff database as efficient tool for flood risk management.
Hydrology and Earth System Sciences Discussion, 10, 2089-2115.
"""

# %% Import libraries
# Vendored from LuMod 0.1.3.0: kernels only; cached Numba compilation.
import math
from math import tanh
import numpy as np
import numba as nb
from pathlib import Path

IUH_DATA = np.loadtxt(Path(__file__).with_name("milc_iuh.txt"))
LOOKUP_TABLE = np.array([
    1, 1, 2, 6, 24, 120, 720, 5040, 40320,
    362880, 3628800, 39916800, 479001600,
    6227020800, 87178291200, 1307674368000,
    20922789888000, 355687428096000, 6402373705728000,
    121645100408832000, 2432902008176640000], dtype="int64")

@nb.njit(cache=True)
def factorial(n):
    # Factorial function to be used in numba functions
    if n > 20:
        raise ValueError
    return LOOKUP_TABLE[n]


@nb.njit(cache=True)
def _actual_evapotranspiration(pet, w, wmax):
    # Compute evpotranspiration
    return max(0.0, pet * w / wmax)


@nb.njit(cache=True)
def _runoff(prec, infil):
    # Compute runoff
    return max(0.0, prec - infil)


@nb.njit(cache=True)
def _infiltration(prec, w, wmax, alpha):
    # Compute infiltlration
    infil = max(0.0, prec * (1. - (w / wmax) ** alpha))
    # Check soil saturation
    if infil + w >= wmax:
        water_excess = infil + w - wmax
        infil -= water_excess
    return infil


@nb.njit(cache=True)
def _baseflow(w, wmax, m, ks, nu):
    # Compute baseflow
    baseflow = (1.0 - nu) * ks * (w / wmax) ** m
    return max(0.0, baseflow)


@nb.njit(cache=True)
def _percolation(w, wmax, m, ks, nu):
    # Compute percolation
    perc = nu * ks * (w / wmax) ** m
    return max(0.0, perc)


@nb.njit(cache=True)
def _water_content(w, wmax):
    # Compute Water Content
    return w / wmax


@nb.njit(cache=True)
def _iuh_comp(gamma, area, dt, delta_T):
    """
    Calculation of geomorphological Instantaneos Unit Hydrograph
    using the geomorphological approach of Gupta et al. (1980)

    Inputs:
        gamma      >   [float] coefficient lag-time relationship
                           Lag = gamma * 1.19 * area ^ 0.33
        area       >   [float] basin area in squared kilometers
        dt         >   [float] computational time step for flood
                           event simulation, in hours
        delta_T    >   [float] input time step of the time series

    Outputs:
        IUH        >   [array] output Instaneous Unit Hydrograph
    """
    lag = (gamma * 1.19 * area ** 0.33) / delta_T
    hp = 0.8 / lag
    t = IUH_DATA[:, 0] * lag
    IUH0 = IUH_DATA[:, 1] * hp
    t_i = np.arange(0, np.max(t), dt)
    IUH = np.interp(t_i, t, IUH0)
    return IUH


@nb.njit(cache=True)
def _iuh_nash(n, gamma, area, dt, delta_T):
    """
    Nash Instantaneos Unit Hydrograph used for baseflow routine

    Inputs:
        n          >   [float] model parameter
        gamma      >   [float] coefficient lag-time relationship
                           Lag = gamma * 1.19 * area ^ 0.33
        area       >   [float] basin area in squared kilometers
        dt         >   [float] computational time step for flood
                           event simulation, in hours
        delta_T    >   [float] input time step of the time series
    """
    K = (gamma * 1.19 * area ** 0.33) / delta_T
    t = np.arange(0, 100, dt)
    IUH = (t / K) ** (n - 1) * np.exp(-t / K) / factorial(int(n - 1)) / K
    return IUH


@nb.njit(cache=True)
def _convolution_giuh(runoff, baseflow, area, gamma, dt):
    """
    Compute streamflow hydrograph at the catchment outlet
    using Geomorphological Instantaneos Unit Hydrograph for runoff and
    Nash Instantaneos Unit Hydrograph for baseflow.

    Inputs:
        runoff     >   [array] runoff serie
        baseflow   >   [array] baseflow serie
        dt         >   [float] computational time step for flood
                           event simulation, in hours
        delta_T    >   [float] input time step of the time series
    """
    delta_T = 24.0    # time delta in hours
    n = len(runoff)

    # Compute runoff IUH
    IUH1 = _iuh_comp(gamma, area, dt, delta_T) * dt
    IUH1 /= np.sum(IUH1)

    # Compute baseflow IUH
    IUH2 = _iuh_nash(1.0, 0.5 * gamma, area, dt, delta_T) * dt
    IUH2 /= np.sum(IUH2)

    # Convolution to compute hydrographs
    qs_int = np.interp(np.arange(0, n, dt), np.arange(n), runoff)
    bf_int = np.interp(np.arange(0, n, dt), np.arange(n), baseflow)

    temp1 = np.convolve(IUH1, qs_int)
    temp2 = np.convolve(IUH2, bf_int)

    # Compute total flow and baseflow
    dt1 = np.round(1. / dt)
    idx = np.arange(0, n * dt1, dt1, dtype=np.int32)

    factor = area * 1000. / delta_T / 3600.
    qd = temp1[idx] * factor  # routed runoff
    qb = temp2[idx] * factor  # routed baseflow

    return qd, qb


@nb.njit(cache=True)
def _milc(prec, pet, area, gamma, w0, wmax, alpha, m, ks, nu, dt):
    """
    Modello Idrologico Lumped in Continuo (MILC)
    """

    # Initial parameters
    n = len(prec)
    w = w0 * wmax  # water storage in mm
    # ks * 24      # convert mm/hr to mm

    # Create empty arrays
    runoff = np.zeros(n, dtype=np.float32)    # direct flow
    baseflow = np.zeros(n, dtype=np.float32)  # baseflow
    et = np.zeros(n, dtype=np.float32)        # evapotranspiration
    infil = np.zeros(n, dtype=np.float32)     # infiltration
    perc = np.zeros(n, dtype=np.float32)      # percolation
    ww = np.zeros(n, dtype=np.float32)        # water content

    for t in range(n):
        # Surface processes
        infil[t] = _infiltration(prec[t], w, wmax, alpha)
        runoff[t] = _runoff(prec[t], infil[t])
        w += infil[t]  # update Water Storage
        # Subsurface processes
        et[t] = _actual_evapotranspiration(pet[t], w, wmax)
        w -= et[t]  # update Water Storage
        # Deep processes
        baseflow[t] = _baseflow(w, wmax, m, ks, nu)
        perc[t] = _percolation(w, wmax, m, ks, nu)
        w -= baseflow[t] + perc[t]  # update Water Storage
        # Compute Water Content
        ww[t] = _water_content(w, wmax)

    # Flow routing using IUH
    qd, qb = _convolution_giuh(runoff, baseflow, area, gamma, dt)
    qt = qb + qd

    return qt, qd, qb, runoff, baseflow, et, infil, perc, ww

Simulation

from basinforge import Basin, get_model

basin = Basin.from_csv("basin.csv", basin_id="A", area_km2=1200,
                       q_unit="m3/s", timestep="daily")
q_mm = get_model("MILC").simulate(basin)
q_m3s = basin.to_m3s(q_mm)

Supply your actual data and catchment area; temperature-dependent models require a temperature column. For non-daily models, choose an appropriate warmup in model steps.

See calibration, input requirements, sources and verification limitations.