Source code for snow

# -*- coding: utf-8 -*-
# =============================================================================
# This file is part of WaterGAP.

# WaterGAP is an opensource software which computes water flows and storages as
# well as water withdrawals and consumptive uses on all continents.

# You should have received a copy of the LGPLv3 License along with WaterGAP.
# if not see <https://www.gnu.org/licenses/lgpl-3.0>
# =============================================================================

"""Optimised snow water storage."""

# =============================================================================
# This module calulates snow water water balance, including snow water storage
# and related fluxes based on section 4.3 of (Müller Schmied et al. (2021)).
# Simulation of the snow dynamics is calculated such that each grid is
# subdivided into 100 non localized subgrids that are assigned different land
# surface elevations according to GTOPO30 (U.S. Geological Survey, 1996).
# For model output, subgrid values are aggregated to 0.5 degree cell value.
# This module uses numba (See https://numba.pydata.org/) to optimizes the
# runtime speed of the snow water storage funtion
# =============================================================================

import numpy as np
from numba import njit

# @njit is a decorator from numba to optimised the python code for speed.


[docs] @njit(cache=True) def snow_water_balance(snow_water_storage, snow_water_storage_subgrid, temperature, precipitation, throughfall, pet_to_soil, land_storage_change_sum, degreeday, current_landarea_frac, landareafrac_ratio, elevation, daily_storage_transfer, adiabatic_lapse_rate, snow_freeze_temp, snow_melt_temp, minstorage_volume, x, y): """ Compute snow water balance for subgrids including snow water storage and water flows entering and leaving the snow storage Parameters ---------- snow_water_storage :float Snow water storage, Units: [mm] snow_water_storage_subgrid : array Snow water storage divided into 100 subgrids based on GTOPO30 (U.S. Geological Survey, 1996) land surface elevation map, Units: [mm] temperature :float Daily temperature climate forcing, Units: [K] precipitation :float Daily precipitation, Units: [mm/day] throughfall :float Throughfall, Units: [mm/day] pet_to_soil :float Remaining energy for addtional soil evaporation, Units: [mm/day] land_storage_change_sum :float Sum of change in vertical balance storages, Units: [mm] degreeday :float Land cover specific degreeday values based on [1]_ .Units: [mm/day/C] current_landarea_frac :float Land area fraction of current time step, Units: [-] landareafrac_ratio :float Ratio of land area fraction of previous to current time step, Units: [-] elevation :array and surface elevation map based on GTOPO30 (U.S. Geological Survey, 1996) [1]_. Units: [m] daily_storage_transfer :float Storage to be transfered to runoff when land area fraction of current time step is zero, Units: [mm] adiabatic_lapse_rate:float Adiabatic lapse rate , Units: [K/m or °C/m] snow_freeze_temp:float Snow freeze temperature , Units: [K] snow_melt_temp:float Snow melt temperature , Units: [K] minstorage_volume: float Volume at which storage is set to zero, units: [km3] x, y : Latititude and longitude indexes of grid cells. Returns ------- snow_water_storage :float Updated snow water storage, Units: [mm] snow_water_storage_subgrid :array Updated snow water storage divided into 100 subgrids based on GTOPO30 (U.S. Geological Survey, 1996) land surface elevation map, Units: mm snow_fall :float Snowfall, Units: [mm/day] sublimation :float Sublimation, Units: [mm/day] snow_melt :float Snow melt, Units: [mm/day] effective_precipitation :float Effective Precipitation, Units: [mm/day] max_temp_elev :float Maximum temperature from the 1st(lowest) elevation, Units: [K] land_storage_change_sum :float Sum of change in vertical balance storages, Units: [mm] daily_storage_transfer :float Updated storage to be transfered to runoff when land area fraction of current time step is zero, Units: [mm] snowcover_frac: float Snow cover fraction References. .. [1] U.S. Geological Survey: USGS EROS archive – digital elevation– global 30 arc-second elevation (GTOPO30), available at: https://www.usgs.gov/centers/eros/science/usgs-eros-archivedigital-elevation-global-30-arc-second-elevation-gtopo30?qtscience_center_objects=0#qt-science_center_objects (last access: 25 MArch 2020), 1996 """ # Index (x, y) to print out varibales of interest # e.g. if x==65 and y==137: print(mean_elevation) # ===================================================================== # Extracting elevation and mean elevation for subcells, Units = m # ===================================================================== mean_elevation = elevation[0] # extracting elevation for sub cells elevation_subgrid = elevation[1:] # Initializing elevation threshold to prevent excessive snow accumulation thresh_elev = 0.0 # ===================================================================== # Creating subgrid variable # ===================================================================== sublimation_subgrid = np.zeros(elevation_subgrid.shape) snow_from_throughfall_subgrid = np.zeros(elevation_subgrid.shape) snow_fall_subgrid = np.zeros(elevation_subgrid.shape) throughfall_before_melt = np.zeros(elevation_subgrid.shape) snow_melt_subgrid = np.zeros(elevation_subgrid.shape) snowcover_frac_subgrid = np.zeros(elevation_subgrid.shape) # ===================================================================== # Calulating temperature (K) for the 100 subcells based on land surface # map elevation and a constant adiabatic lapse rate temp_elev = temperature - ((elevation_subgrid - mean_elevation) * adiabatic_lapse_rate) if current_landarea_frac > 0: # Adapting snow_water_storage_subgrid to dynamic land area fraction snow_water_storage_subgrid *= landareafrac_ratio # minimal storage volume =1e15 (smaller volumes set to zero) # to counter numerical inaccuracies mask_zero = np.abs(snow_water_storage_subgrid) <= minstorage_volume snow_water_storage_subgrid[mask_zero] = 0 # Note!!. # Use .copy() to create initial_storage as a separate copy of snow_water_storage_subgrid. # Without .copy(), changes to the snow_water_storage_subgrid array will also affect # initial_storage since arrays are mutable and share the same memory reference. # For single float values, copying is not required because they are immutable. initial_storage = snow_water_storage_subgrid.copy() for i in range(len(elevation_subgrid)): # ================================================================= # Prevent excessive snow accumulation: # When snow storage reaches 1000 mm in a subcell in a previous # timestep, its elevation is used as a threshold for remaning upper # subcells (those in higher elevations). # New temperature is calulated for these remaning subcells so that # temperature doesn't decrease in these remaining subcells # This should prevent uncontrolled snow accumulation. # ================================================================= # Getting elevation threshold (thresh_elev) from subcell when snow # storage reaches 1000 mm in this subcell if snow_water_storage_subgrid[i] > 1000: if thresh_elev == 0.0: thresh_elev = elevation_subgrid[i] elif thresh_elev > 0: temp_elev[i] = \ temperature - ((thresh_elev - mean_elevation) * adiabatic_lapse_rate) if temp_elev[i] <= snow_freeze_temp: # Computing snow fall(mm) [defined as direct precipitation that # falls as snow] and snow from through fall(mm) snow_fall_subgrid[i] = precipitation snow_from_throughfall_subgrid[i] = throughfall throughfall_before_melt[i] = 0 # Compute sublimation (mm), and updating snow water storage snow_water_storage_subgrid[i] += \ snow_from_throughfall_subgrid[i] # Sublimation is limited by snow storage. # Similar to EQ.14 in H. Müller Schmied et al 2021. if snow_water_storage_subgrid[i] > pet_to_soil: sublimation_subgrid[i] = pet_to_soil snow_water_storage_subgrid[i] -= pet_to_soil else: sublimation_subgrid[i] = snow_water_storage_subgrid[i] snow_water_storage_subgrid[i] = 0 else: throughfall_before_melt[i] = throughfall # Compute snow melt (mm) and update snow water storage (mm) if temp_elev[i] > snow_melt_temp: if snow_water_storage_subgrid[i] > 0: snow_melt_subgrid[i] = \ degreeday * (temp_elev[i] - snow_melt_temp) if snow_melt_subgrid[i] > snow_water_storage_subgrid[i]: snow_melt_subgrid[i] = snow_water_storage_subgrid[i] snow_water_storage_subgrid[i] = 0 else: snow_water_storage_subgrid[i] -= snow_melt_subgrid[i] # ================================================================= # Maximum Temperature based on elevation is used in soil actual # evapotransipration calculation. # ================================================================== # Maximum temperature is taking from the 1st(lowest) elevation if i == 0: max_temp_elev = temp_elev[i] # ===================================================================== # Compute snow cover fraction # ===================================================================== snowcover_frac_subgrid = np.where(snow_water_storage_subgrid > 0, 1, 0) # ================================================================= # Compute effective precipitation based on elevation (mm/day). # and change in snow water storage(mm) # ================================================================== # effective_precipitation is needed as input soil water balance. effective_precipitation_elev = \ snow_melt_subgrid + throughfall_before_melt # computing change in snow_water_storage snow_water_storage_change = \ snow_water_storage_subgrid - initial_storage # ================================================================= # Aggretating subcells values to 0.5 degree cells. # ================================================================= # land_storage_change_sum variable is the sum of all vertical water # balance storage change (canopy, snow, soil) land_storage_change_sum += (np.sum(snow_water_storage_change) / len(elevation_subgrid)) snow_water_storage = \ (np.sum(snow_water_storage_subgrid) / len(elevation_subgrid)) snow_fall = (np.sum(snow_fall_subgrid) / len(elevation_subgrid)) sublimation = (np.sum(sublimation_subgrid) / len(elevation_subgrid)) snow_melt = (np.sum(snow_melt_subgrid) / len(elevation_subgrid)) effective_precipitation = \ (np.sum(effective_precipitation_elev) / len(elevation_subgrid)) snowcover_frac = \ (np.sum(snowcover_frac_subgrid) / len(elevation_subgrid)) else: # ================================================================= # Check if current_landarea_frac == 0 , then add previous storage # to daily_storage_tranfer. This storage will then added to runoff # (e.g. island) # ================================================================= daily_storage_transfer += \ (np.sum(snow_water_storage_subgrid) / len(elevation_subgrid)) snow_water_storage_subgrid[:] = 0 snow_water_storage = 0 return snow_water_storage, snow_water_storage_subgrid, snow_fall, \ sublimation, snow_melt, effective_precipitation, max_temp_elev,\ land_storage_change_sum, daily_storage_transfer, snowcover_frac