Source code for hydromodpy.modeling.modflow

# -*- coding: utf-8 -*-
"""
 * Copyright (C) 2023-2025 Alexandre Gauvain, Ronan Abhervé, Jean-Raynald de Dreuzy
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
 * which is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
"""

#%% LIBRAIRIES

# Python
import flopy
import numpy as np
import os
import datetime
import pandas as pd
import sys
import rasterio
from os.path import dirname, abspath
import matplotlib.pyplot as plt
import flopy.utils.binaryfile as fpu
import flopy.utils.postprocessing as pp

# Root
df = dirname(dirname(abspath(__file__)))
sys.path.append(df)

# HydroModPy
from hydromodpy.tools import toolbox, get_logger
from hydromodpy.modeling import masstransfer

logger = get_logger(__name__)

import matplotlib as mpl
from mpl_toolkits.axes_grid1 import make_axes_locatable

#%% CLASS

[docs] class Modflow: """ Class Modflow. To build, run the hydrologic model and manage/format simulation outputs. """ def __init__(self, geographic: object, # Worflow settings model_folder: str='HydroModPy_outputs', model_name: str='Default', bin_path: str='bin', box: bool=True, sink_fill: bool=False, sim_state: str='steady', plot_cross: bool=True, cross_ylim: list=[], check_grid: bool=True, # Climatic settings recharge=0.001, runoff=None, first_clim: str='mean', dis_perlen: bool=False, # Hydraulic settings nlay: int=1, lay_decay: float=1., bottom: float=None, thick: float=100., verti_hk=None, verti_sy=None, verti_ss=None, hk_value=0.0864, sy_value: float=0.1, ss_value: float=1e-5, hk_decay: list=[0.,None,False,[]], sy_decay: list=[0.,None,False,[]], ss_decay: list=[0.,None,False,[]], vka: float=1.0, exdp: float=1, # Well settings well_coords: list=[], well_fluxes: list=[], # Boundary settings cond_drain: float=None, sea_level=None, bc_left: float=None, bc_right: float=None): """ Initialize method. Parameters ---------- geographic : object Object geographic build by HydroModPy. model_folder : str, optional Path where the model will be store. The default is 'HydroModPy_outputs'. model_name : str, optional Name of the model. The default is 'Default'. bin_path : str, optional Location folder of the modflow executables. The default is 'bin'. box : bool, optional True if you want run the model on the square area of the watershed. The default is True. sink_fill : bool, optional If True, package drain is desactivate on pit. The watertable can create lake on pit. The default is False. sim_state : str, optional 'steady' or 'transient'. simulation state. The default is 'steady'. plot_cross : bool, optional If True, display a cross section of the model. The default is True. check_grid : bool, optional If True, check if the water connectivity is respected with the meshgrid. The default is True. recharge : float or list, optional Recharge [L/T] as input of the model. The default is 0.001. runoff : float or list, optional Runoff [L/T] as an independent variable that can be added in post-processing to the model. The default is 0.0001. first_clim : str, optional If 'mean': the first recharge value is the mean of the chronicle. If 'first': the first recharge value is the first value of the timeseries. If a 'float' : the first recharge is the fixed value. The default is 'mean'. nlay : int, optional Number of layer. The default is 1. lay_decay : float, optional Modification of layer thickness for exponentially decreasing whit depth. The default is 1. bottom : float, optional At this elevation, fix a flat no flow boundary at the bottom of the model. The default is None. thick : float, optional Constant aquifer thickness of the the tickness of the model (if bottom is None). The default is 100. verti_hk : list, optional Depth-dependent hydraulic conductivity. The default is None. verti_sy : list, optional Depth-dependent specific yield. The default is None. verti_ss : list, optional Depth-dependent specific storage. The default is None. hk_value : float or 2D float Fix the hydraulic conductivity value. default is 0.0864. sy_value : float or 2D float, optional Fixe the specific yield value. The default is 0.1. ss_value : float or 2D float, optional Fixe the specifc storage value. Activated for confined layers. The default is 1e-5 (1/day). hk_decay : float, optional Exponential decay of hydraulic conductivity whith depth. The default is 0. sy_decay : float, optional Exponential specific yield of hydraulic conductivity whith depth. The default is 0. ss_decay : float, optional Exponential specific storage of hydraulic conductivity whith depth. The default is 0. vka : list, optional Ratio of horizontal to vertical hydraulic conductivity. The default is 1. wells_coord : list Inform the outlet coordinates of wells [lay,row,col]. Example for 2 wells: [ [1,20,30], [1,15,15] ] wells_fluxes : list Inform the fluxes [L3/T] for each stress-periods, for different wells. Example for 2 wells and 5 stress-periods: [ [-100,0,-100,0,-100], [-100,0,-100,0,-100] ] cond_drain : float, optional Fix the conductance value of the drai (DRN) package. The default is None. sea_level : float, optional Fix head on each cell below this value. The default is None. bc_left : float, optional Fix head on the left border of the domain. The default is None. bc_right : float, optional Fix head on the right border of the domain. The default is None. """ #%% Initialization paths self.model_folder = model_folder if not os.path.exists(self.model_folder): toolbox.create(self.model_folder) self.model_name = model_name if (sys.platform == 'win32') or (sys.platform == 'win64'): self.exe = os.path.join(bin_path, 'win' ,'mfnwt.exe') if (sys.platform == 'linux'): self.exe = os.path.join(bin_path, 'linux' ,'mfnwt') if (sys.platform == 'darwin'): self.exe = os.path.join(bin_path, 'mac' ,'mfnwt') self.full_path = os.path.join(model_folder, model_name) #'modraw' #%% Domain definition # General self.geographic = geographic ####################################################################### #self.geographic.watershed_dem = 'C:/Users/rabherve/Simulations/Lasset/Lasset_25m/results_stable/geographic/watershed_dem.tif' #self.geographic.watershed_box_buff_dem = 'C:/Users/rabherve/Simulations/Lasset/Lasset_25m/results_stable/geographic/watershed_box_buff_dem.tif' ####################################################################### self.resolution = geographic.resolution self.xul = geographic.xmin self.yul = geographic.ymax self.sink_fill = sink_fill try : self.sink = geographic.depressions_data except: pass # Enlarges the modeled domain self.box = box if box == True: self.dem = geographic.dem_box_data self.dem_watershed_path = geographic.watershed_box_buff_dem else: self.dem = geographic.dem_data self.dem_watershed_path = geographic.watershed_buff_dem self.dem[self.dem<=-9999] = -9999 self.dem[self.dem>=9999] = -9999 # Discretization: by default, the number of rows and columns is the DEM discretization self.nrow = self.dem.shape[0] self.ncol = self.dem.shape[1] #%% Boundary conditions self.bc_left = bc_left self.bc_right = bc_right self.sea_level = sea_level try: if self.sea_level == None: self.dem[(self.dem<0)&(self.dem>-200)] = 0 except: pass #%% Input and discretization termes self.recharge = recharge self.runoff = runoff self.sim_state = sim_state self.first_clim = first_clim self.dis_perlen = dis_perlen #%% Model parameters self.bottom = bottom self.thick = thick self.nlay = nlay self.lay_decay = lay_decay self.hk_value = hk_value self.hk_decay = hk_decay self.verti_hk = verti_hk self.vka = vka self.exdp = exdp self.sy_value = sy_value self.sy_decay = sy_decay self.verti_sy = verti_sy self.ss_value = ss_value self.ss_decay = ss_decay self.verti_ss = verti_ss self.cond_drain = cond_drain #%% Specific case implementation # Preprocess conductivity values try: # This tips can be used to inactive some cells from hk_values grid self.dem[self.hk_value<0]=-9999 except: pass #%% Plot things self.plot_cross = plot_cross self.cross_ylim = cross_ylim self.check_grid = check_grid #%% Well settings self.well_coords = well_coords self.well_fluxes = well_fluxes #%% PRE-PROCESSING
[docs] def pre_processing(self): """ Pre-processing to build the hydrologic model. Returns ------- None. """ #%% Initialization # Flopy initialization of Modflow model # ---- flopy.modflow.Modflow self.mf = flopy.modflow.Modflow(self.model_name, exe_name=self.exe, version='mfnwt', listunit=2, verbose=False, model_ws=self.full_path) # external_path=self.full_path # Uses Nwt for Modflow 2005, necessary for unconfined aquifers (improved interactions between surface and aquifer) # Sets up numerical parameters # ---- flopy.modflow.ModflowNwt self.nwt = flopy.modflow.ModflowNwt(self.mf, # headtol=1e-5*(np.nanmax(self.dem)-np.nanmin(self.dem)), # 1e-4 # fluxtol=1e-3*np.nanmean(self.recharge)*self.resolution*self.resolution, # 500 headtol=1e-4, # default 1e-4 fluxtol=500, # default 500 maxiterout=5000, thickfact=1e-05, linmeth=1, iprnwt=1, ibotav=1, options='COMPLEX', Continue=False, backflag=0, stoptol=1e-10 # 1e-10 ) #%% Discretization ### Temporal: time step is driven by recharge # Steady state if self.sim_state == 'steady': self.nper = 1 # Number of forcing periods (recharge) self.perlen = 1 # Length of period self.nstp = [1] # Steps in a given period (not used here) self.steady = True # Steady state self.start_datetime = None # Transient state if self.sim_state == 'transient': if isinstance(self.recharge,(dict))==True: self.start_datetime = 0 else: self.start_datetime = self.recharge.index[0] # First date of recharge self.steady = np.zeros(len(self.recharge),dtype=bool) # Vector of booleans (transient state at each time step) self.steady[0] = True # Steady state for the first time step (initialization of head values by a steady state) self.nstp = np.ones(len(self.recharge)) # One step per time step self.nper = len(self.recharge) # Definition of period duration (forcing is constant on a period) # As many periods as recharge values # Extracts from climatic data the time steps (self.perlen) if self.dis_perlen == True: if isinstance(self.recharge, pd.core.series.Series): if isinstance(self.recharge.index[0], datetime.datetime): self.perlen = self.recharge.index.to_series().diff().dt.total_seconds().values/86400 # values converted into float days else: self.perlen = self.recharge.index.to_series().diff().values if isinstance(self.dis_perlen, list) == True: self.perlen = self.dis_perlen if self.dis_perlen == False: self.perlen = np.ones(len(self.recharge)) if isinstance(self.recharge,(dict))==True: self.perlen = np.ones(len(self.recharge)) # First timestep is steady state: self.perlen[0] = 1 ### Sptial: model domain definition and discretization # Bottom definition for each of the layers self.zbot = np.ones((self.nlay, self.nrow, self.ncol)) if self.bottom is None: self.bottom_layer = self.dem - self.thick # Matrix for constant thickness case self.bottom_layer[self.dem<=-9999]=-9999 else: if isinstance(self.bottom,(int,float))==True: self.bottom_layer = self.bottom # Float for flat bottom case or 2D else: if len(self.bottom.shape) == 2: self.bottom_layer = self.bottom self.bottom_layer[self.dem<=-9999]=-9999 # Modification of layer thickness exponentially if self.lay_decay != 1.: exp_scale = 1-self.lay_decay**self.nlay # Parameters for proportions of bottom layer to surface values for i in range(1, self.nlay+1): if self.lay_decay <= 1: p = i / self.nlay # Uniform thicknesses else: p = (1-self.lay_decay**i) / exp_scale # Increasing thicknesses with depth # Weighted formula to go from bottom_layer to surface (self.dem) if i == 1: self.zbot[i-1] = self.dem - ((self.dem - self.bottom_layer) * p) else: self.zbot[i-1] = self.bottom_layer * p + self.dem * (1-p) # Imposes discretization to modflow model through # ---- flopy.modflow.ModflowDis self.dis = flopy.modflow.ModflowDis(self.mf, itmuni=0, # itmuni = 0 ==> undefined lenuni=2, # itmuni_values = {'days': 4, 'hours': 3, 'minutes': 2, 'seconds': 1, 'undefined': 0, 'years': 5} nlay=self.nlay, nrow=self.nrow, ncol=self.ncol, delr=self.resolution, delc=self.resolution, top=self.dem, botm=self.zbot, xul=self.xul, yul=self.yul, nper=self.nper, perlen=self.perlen, nstp=self.nstp, steady=self.steady, start_datetime=self.start_datetime) #%% Boundary conditions ### Constant head boundary conditions of no flow (sides of domain) self.iboundData = np.ones((self.nlay, self.nrow, self.ncol)) # iboundData=1: Should compute head in cells # iboundData=0: Nothing is calculated in cells # iboundData=-1: Values imposed at the value of strtData # Free surface level is set to the surface (altitude of DEM) self.strtData = np.ones((self.nlay, self.nrow, self.ncol))* self.dem # Fixed head on the left (better for square domain) if isinstance(self.bc_left,(int,float)) == True: self.iboundData[:,:,0] = -1 self.strtData[:,:,0] = self.bc_left # Fixed head on the right (better for square domain) if isinstance(self.bc_right,(int,float)) == True: self.iboundData[:,:,-1] = -1 self.strtData[:,:,-1] = self.bc_right # No flow boundary conditions for i in range (self.nlay): if isinstance(self.sea_level,(int,float)) == True: self.iboundData[i][self.dem <= self.sea_level] = -1 self.strtData[self.iboundData == -1] = self.sea_level self.iboundData[i][self.dem < -1000] = 0 # 0 is for NO FLOW # ---- flopy.modflow.ModflowBas self.bas = flopy.modflow.ModflowBas(self.mf, ibound=self.iboundData, strt=self.strtData, hnoflo=-9999) ### Initialze the top boundary condition of DRN package self.drain_array = np.ones((self.nrow, self.ncol)) ### Constant head boundary conditions of no f : specific for sea level if isinstance(self.sea_level, (int,float,pd.Series,list)) == True: package = np.zeros((self.nper,self.nrow, self.ncol)) if isinstance(self.sea_level,(int,float)) == False: self.chData = {} for kper in range(0, self.nper): chdKper = [] for i in range (0,self.nrow): for j in range (0, self.ncol): if self.dem[i,j] < np.max(self.sea_level): if self.iboundData[0,i,j] != 0: # no-flow cells cannot be converted to specified head cells self.drain_array[i,j] = 0 package[kper,i,j] = 1 chdKper.append([0,i,j,self.sea_level[kper],self.sea_level[kper]]) self.chData[kper] = chdKper # ---- flopy.modflow.ModflowChd self.chd = flopy.modflow.ModflowChd(self.mf, stress_period_data=self.chData) #%% Parametrization # Specify the unconfined conditions of the aquifer self.laywet = np.zeros(self.nlay) # wettable self.laytype = np.ones(self.nlay) # convertible # Necessary to give hydraulic conductivity: 3D matrix of hydraulic conductivities # Homogeneous or heterogeneous hydraulic conductivity # self.hk_value is always a 3D matrix create from hydraulic.py ### FUNCTION FOR GRADIENT DECAY LINKED TO DEM ELEVATION def compute_values(dem, dem_min, dem_max, dcal, dadj): # Compute boundary values val_min = (1/dcal) val_max = (1/dcal) + dadj # Ensure positive denominator if val_max <0: val_max=1 result = np.ones((dem.shape[0], dem.shape[1])) result = np.where((dem>dem_min) & (dem<dem_max), (val_min + (val_max - val_min) * ((dem - dem_min) / (dem_max - dem_min))), result) result = np.where(dem <= dem_min, val_min, result) result = np.where(dem >= dem_max, val_max, result) result[result<=0] = val_max return result ### Hydraulic conductivty self.hk = np.ones((self.nlay, self.nrow, self.ncol))*self.hk_value # Exponential decay if self.hk_decay[0] != 0: grad_elev = self.hk_decay[3] if grad_elev != []: dem_min = grad_elev[0] dem_max = grad_elev[1] dadj = grad_elev[2] dcal = self.hk_decay[0] self.kdec_inv = compute_values(self.dem, dem_min, dem_max, dcal, dadj) self.kdec = 1/self.kdec_inv else: self.kdec = self.hk_decay[0] kmin = self.hk_decay[1] kmax = self.hk_value hklog_transf = self.hk_decay[2] if kmin == None: depth = np.zeros(self.hk.shape) depth[1:,:,:] = self.dem - self.zbot[:-1,:,:] self.hk *= np.exp(-self.kdec*depth) logger.debug('Decay without Kmin') if kmin != None: depth = np.zeros(self.hk.shape) depth[1:,:,:] = self.dem - self.zbot[1:,:,:] # self.zbot[:-1,:,:] self.hk = (kmin)+((kmax)-(kmin))*np.exp(-self.kdec*depth) # self.hk[self.hk<kmin] = kmin logger.debug('Decay with Kmin') if (kmin != None) and (hklog_transf==True): depth = np.zeros(self.hk.shape) depth[1:,:,:] = self.dem - self.zbot[1:,:,:] # self.zbot[:-1,:,:] self.hk = np.log10(kmin)+(np.log10(kmax)-np.log10(kmin))*np.exp(-self.kdec*depth) self.hk = 10**self.hk logger.debug('Decay with Kmin and log transform') # self.hk[self.hk<10**kmin] = 10**kmin # Define values for some thickness (disconnected from the vertical discretization) if self.verti_hk != None: for j in range(len(self.verti_hk)): logger.debug('Processing verti_hk layer j=%d', j) for i in range(len(self.zbot)): logger.debug('Processing zbot layer i=%d', i) k_val = self.verti_hk[j][0] d1 = self.verti_hk[j][1][0] d2 = self.verti_hk[j][1][1] hk_d1 = (self.dem - d1) hk_d2 = (self.dem - d2) mask = ((self.zbot[i] <= hk_d1) & (self.zbot[i] >= hk_d2)) self.hk[i][mask] = k_val logger.debug('Applied k_val=%s', k_val) ### Specific yield self.sy = np.ones((self.nlay, self.nrow, self.ncol))*self.sy_value # Exponential decay if self.sy_decay[0] != 0: grad_elev = self.sy_decay[3] if grad_elev != []: dem_min = grad_elev[0] dem_max = grad_elev[1] dadj = grad_elev[2] dcal = self.sy_decay[0] self.sydec_inv = compute_values(self.dem, dem_min, dem_max, dcal, dadj) self.sydec = 1/self.sydec_inv else: self.sydec = self.sy_decay[0] symin = self.sy_decay[1] symax = self.sy_value sylog_transf = self.sy_decay[2] if symin == None: depth = np.zeros(self.sy.shape) depth[1:,:,:] = self.dem - self.zbot[:-1,:,:] self.sy *= np.exp(-self.sydec*depth) if symin != None: depth = np.zeros(self.sy.shape) depth[1:,:,:] = self.dem - self.zbot[1:,:,:] self.sy = (symin)+((symax)-(symin))*np.exp(-self.sydec*depth) # self.sy[self.sy<symin] = symin if (symin != None) and (sylog_transf==True): depth = np.zeros(self.sy.shape) depth[1:,:,:] = self.dem - self.zbot[:-1,:,:] self.sy = np.log10(symin)+(np.log10(symax)-np.log10(symin))*np.exp(-self.sydec*depth) self.sy = 10**self.sy # self.sy[self.sy<10**symin] = 10**symin # Define values for some thickness (disconnected from the vertical discretization) if self.verti_sy != None: for j in range(len(self.verti_sy)): logger.debug('Processing verti_sy layer j=%d', j) for i in range(len(self.zbot)): logger.debug('Processing zbot layer i=%d', i) sy_val = self.verti_sy[j][0] d1 = self.verti_sy[j][1][0] d2 = self.verti_sy[j][1][1] sy_d1 = (self.dem - d1) sy_d2 = (self.dem - d2) mask = ((self.zbot[i] <= sy_d1) & (self.zbot[i] >= sy_d2)) self.sy[i][mask] = sy_val logger.debug('Applied sy_val=%s', sy_val) ### Specific storage self.ss = np.ones((self.nlay, self.nrow, self.ncol))*self.ss_value # Exponential decay if self.ss_decay[0] != 0: grad_elev = self.ss_decay[3] if grad_elev != []: dem_min = grad_elev[0] dem_max = grad_elev[1] dadj = grad_elev[2] dcal = self.ss_decay[0] self.ssdec_inv = compute_values(self.dem, dem_min, dem_max, dcal, dadj) self.ssdec = 1/self.ssdec_inv else: self.ssdec = self.ss_decay[0] ssmin = self.ss_decay[1] ssmax = self.ss_value sslog_transf = self.ss_decay[2] if symin == None: depth = np.zeros(self.ss.shape) depth[1:,:,:] = self.dem - self.zbot[:-1,:,:] self.ss *= np.exp(-self.ssdec*depth) if symin != None: depth = np.zeros(self.ss.shape) depth[1:,:,:] = self.dem - self.zbot[1:,:,:] self.ss = (ssmin)+((ssmax)-(ssmin))*np.exp(-self.ssdec*depth) # self.ss[self.ss<ssmin] = ssmin if (symin != None) and (sslog_transf==True): depth = np.zeros(self.ss.shape) depth[1:,:,:] = self.dem - self.zbot[1:,:,:] self.ss = np.log10(ssmin)+(np.log10(ssmax)-np.log10(ssmin))*np.exp(-self.ssdec*depth) self.ss = 10**self.ss # self.ss[self.ss<10**ssmin] = 10**ssmin # Define values for some thickness (disconnected from the vertical discretization) if self.verti_ss != None: for j in range(len(self.verti_ss)): logger.debug('Processing verti_ss layer j=%d', j) for i in range(len(self.zbot)): logger.debug('Processing zbot layer i=%d', i) ss_val = self.verti_ss[j][0] d1 = self.verti_ss[j][1][0] d2 = self.verti_ss[j][1][1] ss_d1 = (self.dem - d1) ss_d2 = (self.dem - d2) mask = ((self.zbot[i] <= ss_d1) & (self.zbot[i] >= ss_d2)) self.ss[i][mask] = ss_val logger.debug('Applied ss_val=%s', ss_val) # ---- flopy.modflow.ModflowUpw self.upw = flopy.modflow.ModflowUpw(self.mf, laytyp=self.laytype, laywet=self.laywet, hk=self.hk, sy=self.sy, ss=self.ss, vka=self.vka, iphdry=1, hdry=-100, layvka=1, # 1: anisotropy ratio, 0: vertical hk in model unit extension='upw', unitnumber=None, # unitnumber=31 noparcheck=False ) #%% Source terms # Activated only when recharge values are negative (king of pumping) if isinstance(self.recharge,(dict))==False: if isinstance(self.recharge,float)==False and (self.recharge < 0).any().any() == True: self.evt = self.recharge.copy() # All positive values are set to 0 (no negative values) self.evt[self.evt>=0] = 0 # All negative values are set to positive values self.evt = abs(self.evt) self.evtData = {} # Loop over all time steps to make a dictionnary from a scalar or a dictionnary for kper in range(0, self.nper): if isinstance(self.evt,(int,float)): # Steady state: self.evtData[kper] = self.evt else: # Transient state: if kper == 0: self.evtData[kper] = 0 else: self.evtData[kper] = self.evt[kper] # ---- flopy.modflow.ModflowEvt self.evt = flopy.modflow.ModflowEvt(self.mf, evtr=self.evtData, surf = self.dem, nevtop = 3, # 1 (top), 2 (layer), 3 (highest active) is default exdp = self.exdp, # default is 1 (1m from surface) ievt = 1, # default: 1 (if layer) ==> activated only if nevtop = 2 ipakcb = 1 # default: 0 ) # Finally sets all negative of self.recharge to zero values for simulation if not isinstance(self.recharge,(int,float)): self.recharge[self.recharge<0] = 0 # Recharge of the aquifer on the top of the water table self.rchData = {} for kper in range(0, self.nper): if isinstance(self.recharge,(dict))==True: if self.sim_state == 'steady': self.rchData = (sum(self.recharge.values()) / len(self.recharge)) if self.sim_state == 'transient': self.rchData = self.recharge else: if isinstance(self.recharge,(int,float)): # Only value in self.climatic (steady) self.rchData[kper] = self.recharge else: if kper == 0: if self.first_clim == 'mean': self.rchData[kper] = np.nanmean(self.recharge) if self.first_clim == 'first': self.rchData[kper] = self.recharge.iloc[0] if isinstance(self.first_clim,(int,float)): self.rchData[kper] = self.first_clim else: # More flexibility in the possible format of the climatic chronicles # Should only be used exceptionnaly (pandas series recommended) try: self.rchData[kper] = self.recharge.iloc[kper] except: self.rchData[kper] = self.recharge.iloc[kper].values[0] # Sets recharge to modflow through flopy # ---- flopy.modflow.ModflowRch self.rch = flopy.modflow.ModflowRch(self.mf, rech=self.rchData) #%% Drain package # DRN is applied to all the surface of the model: enables seepage on the top layer self.drnData = np.zeros((int(np.sum(self.drain_array)), 5)) compt = 0 self.drnData[:, 0] = 0 # First value (0): layer for i in range (0,self.nrow): for j in range (0, self.ncol): if self.drain_array[i,j] == 1: self.drnData[compt, 1] = i # Second value (1): row number self.drnData[compt, 2] = j # Third value (2): column number self.drnData[compt, 3]= self.dem[i, j] # Fourth value (3): altitude # Fifth value (4): value of the conductivity of the drain (integrated over the surface of the cell) if self.sink_fill == False: if self.cond_drain != None: self.drnData[compt, 4] = self.cond_drain else: self.drnData[compt, 4] = (self.hk[0, i, j] * self.resolution** 2) else: if self.sink[i,j]>0: self.drnData[compt, 4] = 0 else: if self.cond_drain != None: self.drnData[compt, 4] = self.cond_drain else: self.drnData[compt, 4] = self.hk[0, i, j] * self.resolution** 2 compt += 1 # Imposes DRN condition to Modflow through flopy lrcec= {0:self.drnData} # ---- flopy.modflow.ModflowDrn self.drn = flopy.modflow.ModflowDrn(self.mf, stress_period_data=lrcec) #%% Well package if (self.well_coords != []) or (len(self.well_coords) > 0): # Number of stress periods n_stress_periods = len(self.recharge) n_wells = len(self.well_coords) # Initialize the dictionary self.lrcq = {} # Populate the dictionary with well data for each stress period for t in range(n_stress_periods): list_t = [] for n in range(n_wells): list_t.append([*self.well_coords[n], self.well_fluxes[n][t]]) self.lrcq[t] = list_t # ---- flopy.modflow.ModflowWel self.wel = flopy.modflow.ModflowWel(self.mf, ipakcb=1, stress_period_data=self.lrcq) #%% Output control stress_period_data = {} for kper in range(self.nper): kstp = self.nstp[kper] # Saves head (hds) and budget (cbc) for each of the stress periods stress_period_data[(kper, kstp-1)] = ['save head', 'save budget'] # ---- flopy.modflow.ModflowOc self.oc = flopy.modflow.ModflowOc(self.mf, stress_period_data=stress_period_data, extension=['oc','hds','cbc'], unitnumber=None, # unitnumber=[14, 51, 52, 53, 0], compact=True) self.oc.reset_budgetunit(fname= self.model_name+'.cbc') # Check grid def check_water_flow_connectivity(grid): layers, rows, cols = grid.shape problematic_cells = [] # Store problematic cells for z in range(layers - 1): # Focus on flow between layers logger.debug('Checking layer %d', z) for y in range(rows): for x in range(cols): # Skip if the current cell is inactive (e.g., NaN or specific inactive value) if np.isnan(grid[z, y, x]) or np.isnan(grid[z+1, y, x]): continue # Current cell's top and bottom elevations current_top = grid[z, y, x] current_bottom = grid[z+1, y, x] neighbors = [] # Collect adjacent neighbors' top and bottom elevations if y > 0 and not (np.isnan(grid[z, y-1, x]) or np.isnan(grid[z+1, y-1, x])): # Left neighbor neighbors.append((grid[z, y-1, x], grid[z+1, y-1, x])) if y < rows - 1 and not (np.isnan(grid[z, y+1, x]) or np.isnan(grid[z+1, y+1, x])): # Right neighbor neighbors.append((grid[z, y+1, x], grid[z+1, y+1, x])) if x > 0 and not (np.isnan(grid[z, y, x-1]) or np.isnan(grid[z+1, y, x-1])): # Front neighbor neighbors.append((grid[z, y, x-1], grid[z+1, y, x-1])) if x < cols - 1 and not (np.isnan(grid[z, y, x+1]) or np.isnan(grid[z+1, y, x+1])): # Back neighbor neighbors.append((grid[z, y, x+1], grid[z+1, y, x+1])) # If there are neighbors, check if water can flow if neighbors: can_flow = False for neighbor_top, neighbor_bottom in neighbors: # Check if current cell's range overlaps with neighbor's range if (current_bottom <= neighbor_top and current_top >= neighbor_bottom): can_flow = True break if not can_flow: problematic_cells.append((z, y, x)) return problematic_cells if self.check_grid == True: grid_to_check = self.mf.modelgrid.top_botm problematic_cells = check_water_flow_connectivity(grid_to_check) if not problematic_cells: logger.info("MODFLOW grid connectivity check passed") self.prob_cells = 0 else: logger.warning( "MODFLOW grid connectivity check found %d problematic cells", len(problematic_cells), ) self.prob_cells = len(problematic_cells) # CrossSection figure if self.plot_cross == True: fig, axs = plt.subplots(1, 2, figsize=(14,4), dpi=300) axs = axs.ravel() grid_model = self.mf.modelgrid modelxsect1 = flopy.plot.PlotCrossSection(model=self.mf, line={'Row': int((grid_model.shape[1])/2)}) imhk = modelxsect1.plot_array(self.hk/24/3600, masked_values=[-9999], cmap='jet', alpha=0.5, lw=0.1, ax=axs[0], # norm=mpl.colors.LogNorm(vmin=self.hk.min(), vmax=self.hk.max()) norm=mpl.colors.LogNorm(vmin=1e-10, vmax=1e-1) ) # modelxsect1.plot_grid(ax=axs[0]) axs[0].set_title('West-East (Row), K [m/s]', fontsize=12) if self.cross_ylim == []: axs[0].set_ylim(np.nanmin(np.ma.masked_equal(self.dem, -9999, copy=False)), np.nanmax(np.ma.masked_equal(self.dem, -9999, copy=False))) else: axs[0].set_ylim(self.cross_ylim[0], self.cross_ylim[1]) axs[0].set_xlabel('Distance [m]') axs[0].set_ylabel('Elevation [m]') # divider = make_axes_locatable(axs[0]) # cax = divider.append_axes('right', size='5%', pad=0.05) # fig.colorbar(imhk, cax=cax, orientation='vertical') fig.colorbar(imhk) modelxsect2 = flopy.plot.PlotCrossSection(model=self.mf, line={'Column': int((grid_model.shape[2])/2)}) imsy = modelxsect2.plot_array(self.sy*100, masked_values=[-9999], cmap='jet', alpha=0.5, lw=0.1, ax=axs[1], # norm=mpl.colors.LogNorm(vmin=self.sy.min(), vmax=self.sy.max()) norm=mpl.colors.LogNorm(vmin=0.1, vmax=100) ) # modelxsect2.plot_grid(ax=axs[1]) axs[1].set_title('North-South (Column), Sy [%]', fontsize=12) if self.cross_ylim == []: axs[1].set_ylim(np.nanmin(np.ma.masked_equal(self.dem, -9999, copy=False)), np.nanmax(np.ma.masked_equal(self.dem, -9999, copy=False))) else: axs[1].set_ylim(self.cross_ylim[0], self.cross_ylim[1]) axs[1].set_xlabel('Distance [m]') axs[1].set_ylabel('Elevation [m]') # divider = make_axes_locatable(axs[1]) # cax = divider.append_axes('right', size='5%', pad=0.05) # fig.colorbar(imsy, cax=cax, orientation='vertical') fig.colorbar(imsy) fig.suptitle(self.model_name.upper(), y=1.0, fontsize=10) fig.tight_layout()
#%% PROCESSING
[docs] def processing(self, write_model:bool=True, run_model:bool=False, link_mt3dms:bool=False): """ Run the hydrologic model. Parameters ---------- write_model : bool, optional Flag to write input files or not. The default is True. run_model : bool, optional Flag to run model or not. The default is False. Returns ------- success_model : bool Flag to know if the simulation is done correctly. """ if link_mt3dms == True: lmt = flopy.modflow.ModflowLmt(self.mf, output_file_name='mt3d_link.ftl', extension='lmt8', output_file_format='unformatted', unitnumber=None) # unitnumber=30 (Luca) # Create modflow files if write_model == True: # Write input files self.mf.write_input() # Run modflow files success_model = False if run_model == True: verbose = True success_model, tempo = self.mf.run_model(silent=not verbose) # True without msg return success_model
#%% POST-PROCESSING
[docs] def post_processing(self, model_modflow:object, watertable_elevation:bool=True, watertable_depth:bool=True, seepage_areas:bool=True, outflow_drain:bool=True, groundwater_flux:bool=True, groundwater_storage:bool=True, accumulation_flux:bool=True, persistency_index:bool=False, intermittency_yearly:bool=False, intermittency_monthly:bool=False, intermittency_weekly:bool=False, intermittency_daily:bool=False, export_all_tif:bool=False): """ Create outputs files. Parameters ---------- model_modflow : object MODFLOW Python object. watertable_elevation : bool, optional Write watertable elevation outputs. The default is True. watertable_depth : bool, optional Write watertable depth outputs. The default is True. seepage_areas : bool, optional Write seepage areas outputs. The default is True. outflow_drain : bool, optional Write outflow drain outputs. The default is True. groundwater_flux : bool, optional Write groundwater flux outputs. The default is True. groundwater_storage : bool, optional Write groundwater storage outputs. The default is True. accumulation_flux : bool, optional Write accumulation flux outputs. The default is True. persistency_index : bool, optional Write persistency index outputs. The default is False. intermittency_monthly : bool, optional Write intermittency monthly outputs. The default is False. intermittency_weekly : bool, optional Write intermittency weekly outputs. The default is False. intermittency_daily : bool, optional Write intermittency daily outputs. The default is False. export_all_tif : bool, optional Write all files .tif at each time step. The default is False. """ # Create folders self.save_file = os.path.join(self.full_path, '_postprocess') toolbox.create_folder(self.save_file) self.figure_file = os.path.join(self.full_path, '_postprocess', '_figures') toolbox.create_folder(self.figure_file) self.temporary_file = os.path.join(self.full_path, '_postprocess','_temporary') toolbox.create_folder(self.temporary_file) self.tifs_file = os.path.join(self.full_path, '_postprocess', '_rasters') toolbox.create_folder(self.tifs_file) self.save_fig = os.path.join(self.model_folder, '_figures') toolbox.create_folder(self.save_fig) #%% Load essential data # Modflow specific files (written in the processing phase) self.path_file = os.path.join(self.full_path, self.model_name) # Files have been output in the processing phase and are re-read here self.dem_mask = (self.dem<-9999) # heads self.head_fpu = fpu.HeadFile(self.path_file+'.hds') # fluxes self.cbb = fpu.CellBudgetFile(self.path_file+'.cbc') # Import times self.times = self.head_fpu.get_times() self.kstpkpers = self.head_fpu.get_kstpkper() # Params model self.nper = self.dis.nper self.kper = np.arange(0,self.nper,1) if len(self.kper) > 1: self.kstp = self.nstp[self.kper] - 1 #%% Export results over times # Fill dictionnaries .npy or .nc over times and create .tif # Create dictionnaries for each of the results to extract # x[time]=matrix # - x: type of output # - time: time at which it is taken # - matrix: 2D matrix of values self.dict_watertable_elevation = {} self.dict_watertable_depth = {} self.dict_seepage_areas = {} self.dict_outflow_drain = {} self.dict_groundwater_flux = {} self.dict_specific_discharge = {} self.dict_accumulation_flux = {} self.dict_groundwater_storage = {} self.dict_persistency_index = {} self.dict_intermittency_yearly = {} self.dict_intermittency_monthly = {} self.dict_intermittency_weekly = {} self.dict_intermittency_daily = {} logger.debug('Post-processing MODFLOW: %s', self.model_name) # Loop over times: fills each of the previous structures and create raster for item, time in enumerate(self.times): logger.info('Post-processing stress period %d/%d', item + 1, len(self.times)) if len(self.times) == 1: self.kstpkper = self.kstpkpers[0] if len(self.times) > 1: self.kstpkper = (self.kstp[item], self.kper[item]) lead_numb = str(item) export_tif = True if export_all_tif == False: if item > 0: export_tif = False # Search watertable data positive values self.head = self.head_fpu.get_data(totim=time) # self.head_all = self.head_fpu.get_alldata(), self.head_all[item][0] if self.nlay == 1: self.head_data = self.head[0] else: ### Option 1 self.head_data = pp.get_water_table(self.head, -100) # -9999 ### Option 2 # head_final = np.zeros([self.nrow,self.ncol]) # for i in range(0,self.nrow): # for j in range (0,self.ncol): # for k in range(0,self.nlay): # if self.head[k,i,j] > 0: # head_final[i,j] = self.head[k,i,j] # break # self.head_data = head_final.copy() if watertable_elevation == True: ### Watertable elevation self.wt_elev = self.head_data.copy() self.wt_elev[self.dem_mask] = -9999 output_path = self.tifs_file+'/watertable_elevation_t('+lead_numb+').tif' if export_tif==True: toolbox.export_tif(self.dem_watershed_path, self.wt_elev, output_path, -9999) self.dict_watertable_elevation[item] = self.wt_elev if watertable_depth == True: ### Watertable depth self.wt_depth = self.dem - self.wt_elev.copy() self.wt_depth[self.dem_mask] = -9999 output_path = self.tifs_file+'/watertable_depth_t('+lead_numb+').tif' if export_tif==True: toolbox.export_tif(self.dem_watershed_path, self.wt_depth, output_path, -9999) self.dict_watertable_depth[item] = self.wt_depth if seepage_areas == True: ### Seepage areas self.seep_area = self.dem - self.wt_elev.copy() self.seep_area[self.seep_area >= 0] = 0 self.seep_area[self.seep_area < 0] = 1 self.seep_area[self.dem_mask] = -9999 output_path = self.tifs_file+'/seepage_areas_t('+lead_numb+').tif' if export_tif==True: toolbox.export_tif(self.dem_watershed_path, self.seep_area, output_path, -9999) self.dict_seepage_areas[item] = self.seep_area if outflow_drain == True: ### Outflow drain self.drain = self.cbb.get_data(text='DRAINS', kstpkper=self.kstpkper, totim=time) self.out_all = np.zeros((1, self.dis.nrow, self.dis.ncol)) sim = 0 count = 0 for i in range(0, self.dis.nrow): for j in range(0, self.dis.ncol): if self.drain_array[i,j] == 1: self.out_all[sim, i, j] = np.abs(self.drain[0][count][1]) count = count + 1 self.out_drn = self.out_all[0] self.out_drn[self.dem_mask] = -9999 output_path = self.tifs_file+'/outflow_drain_t('+lead_numb+').tif' if accumulation_flux==True: toolbox.export_tif(self.dem_watershed_path, self.out_drn, output_path, -9999) else: if export_tif==True: toolbox.export_tif(self.dem_watershed_path, self.out_drn, output_path, -9999) self.dict_outflow_drain[item] = self.out_drn if groundwater_flux == True: ### Groundwater flux self.cbb_data = self.cbb.get_data(kstpkper=(0, 0)) self.frf = self.cbb.get_data(text='FLOW RIGHT FACE', kstpkper=self.kstpkper, totim=time)[0] self.fff = self.cbb.get_data(text='FLOW FRONT FACE', kstpkper=self.kstpkper, totim=time)[0] if self.nlay == 1: self.flux = np.sqrt(self.frf**2 + self.fff**2) if self.nlay > 1: self.flf = self.cbb.get_data(text='FLOW LOWER FACE', kstpkper=self.kstpkper, totim=time)[0] # > 1 lay self.flux = np.sqrt(self.frf**2 + self.fff**2 + self.flf**2) self.flux_top = self.flux[0] self.flux_top[self.dem_mask] = -9999 output_path = self.tifs_file+'/groundwater_flux_t('+lead_numb+').tif' if export_tif==True: toolbox.export_tif(self.dem_watershed_path, self.flux_top, output_path, -9999) self.dict_groundwater_flux[item] = self.flux_top if groundwater_storage == True: ### Groundwater storage self.wt_sto = self.wt_elev.copy() self.wt_sto[self.dem<0] = np.nan self.wt_sto = ( self.wt_sto - self.zbot[-1] ) * (self.resolution**2) * np.nanmean(self.sy) output_path = self.tifs_file+'/groundwater_storage_t('+lead_numb+').tif' if export_tif==True: toolbox.export_tif(self.dem_watershed_path, self.wt_sto, output_path, -9999) self.dict_groundwater_storage[item] = self.wt_sto if accumulation_flux == True: ### Accumulation flux accumulated_flow = masstransfer.Masstransfer(self.geographic, 'outflow_drain_t('+lead_numb+').tif', 'tracept_t('+lead_numb+').shp', 'accumulation_flux_t('+lead_numb+').tif', extraction_folder=self.save_file) accumulated_flow.trace_cumulated() output_path = self.tifs_file+'/accumulation_flux_t('+lead_numb+').tif' with rasterio.open(output_path) as src: self.dict_accumulation_flux[item] = src.read(1) ### Save dictionaries to npy if watertable_elevation == True: logger.info('Exporting watertable elevation time series') np.save(self.save_file+'/watertable_elevation', self.dict_watertable_elevation) if watertable_depth == True: logger.info('Exporting watertable depth time series') np.save(self.save_file+'/watertable_depth', self.dict_watertable_depth) if seepage_areas == True: logger.info('Exporting seepage areas time series') np.save(self.save_file+'/seepage_areas', self.dict_seepage_areas) if outflow_drain == True: logger.info('Exporting outflow drain time series') np.save(self.save_file+'/outflow_drain', self.dict_outflow_drain) if groundwater_flux == True: logger.info('Exporting groundwater flux time series') np.save(self.save_file+'/groundwater_flux', self.dict_groundwater_flux) if groundwater_storage == True: logger.info('Exporting groundwater storage time series') np.save(self.save_file+'/groundwater_storage', self.dict_groundwater_storage) if accumulation_flux == True: logger.info('Exporting accumulation flux time series') np.save(self.save_file+'/accumulation_flux', self.dict_accumulation_flux) if persistency_index == True: ### Persistency index logger.info('Exporting persistency index maps') acc_npy_raw = np.load(os.path.join(self.save_file,'accumulation_flux.npy'), allow_pickle=True).item() acc_npy = list(acc_npy_raw.items())[:] for key in range(len(acc_npy)): with rasterio.open(self.geographic.watershed_box_buff_dem) as src: mask = src.read(1) acc_npy[key] = np.ma.masked_array(acc_npy[key][1], mask=(mask<0)) zero = acc_npy[0] * 0 for i in range(len(acc_npy)): tempo = acc_npy[i].copy() tempo[tempo>0] = 1 zero = zero + tempo days_flux = zero.copy() / len(acc_npy) pi_export = days_flux.copy() self.pi = np.ma.masked_where(days_flux <= 0, days_flux) self.dict_persistency_index[0] = self.pi pi_export[days_flux <= 0] = -9999 pi_export[mask<=0] = -9999 output_path = self.tifs_file+'/persistency_index_t('+'-'+').tif' toolbox.export_tif(self.dem_watershed_path, pi_export, output_path, -9999) np.save(self.save_file+'/persistency_index', self.dict_persistency_index) if intermittency_daily == True: ### Intermittency daily logger.info('Exporting daily intermittency maps') acc_npy_raw = np.load(os.path.join(self.save_file, 'accumulation_flux.npy'), allow_pickle=True).item() acc_npy = list(acc_npy_raw.items())[:] if len(acc_npy_raw)>=365: inf = 0 sup = 365 step = int(round(len(acc_npy_raw)/365)) compt=0 for i in range(step): logger.debug('Processing daily intermittency t: %d / %d', i, step) interv = list(acc_npy)[inf:sup] for key in range(len(interv)): with rasterio.open(self.geographic.watershed_dem) as src: mask = src.read(1) interv[key] = np.ma.masked_array(interv[key][1], mask=(mask<0)) zero = acc_npy_raw[0] * 0 for j in range(len(interv)): tempo = interv[j].copy() tempo[tempo>0] = 1 zero = zero + tempo days_flux = zero.copy() days_flux = np.ma.masked_array(days_flux, mask=(mask<0)) days_flux = np.ma.masked_array(days_flux, mask=(days_flux<=0)) for k in range(len(interv)): tempo = np.ma.masked_where(interv[k]<=0, interv[k]) tempo[days_flux<365] = 0 tempo[days_flux==365] = 1 tempo_export = tempo.copy() self.tempo = np.ma.masked_where(interv[k]<=0, tempo) self.dict_intermittency_daily[compt] = self.tempo tempo_export[interv[k]<=0] = -9999 tempo_export[mask<=0] = -9999 output_path = self.tifs_file+'/intermittency_daily_t('+str(compt)+').tif' # if export_tif==True: toolbox.export_tif(self.geographic.watershed_dem, tempo_export, output_path, -9999) compt+=1 inf+=365 sup+=365 np.save(self.save_file+'/intermittency_daily', self.dict_intermittency_daily) if intermittency_weekly == True: logger.info('Exporting weekly intermittency maps') acc_npy_raw = np.load(os.path.join(self.save_file, 'accumulation_flux.npy'), allow_pickle=True).item() acc_npy = list(acc_npy_raw.items())[:] if len(acc_npy_raw)>=52: inf = 0 sup = 52 step = int(round(len(acc_npy_raw)/52)) compt=0 for i in range(step): logger.debug('Processing weekly intermittency t: %d / %d', i, step) interv = list(acc_npy)[inf:sup] for key in range(len(interv)): with rasterio.open(self.geographic.watershed_dem) as src: mask = src.read(1) interv[key] = np.ma.masked_array(interv[key][1], mask=(mask<0)) zero = acc_npy_raw[0] * 0 for j in range(len(interv)): tempo = interv[j].copy() tempo[tempo>0] = 1 zero = zero + tempo days_flux = zero.copy() days_flux = np.ma.masked_array(days_flux, mask=(mask<0)) days_flux = np.ma.masked_array(days_flux, mask=(days_flux<=0)) for k in range(len(interv)): tempo = np.ma.masked_where(interv[k]<=0, interv[k]) tempo[days_flux<52] = 0 tempo[days_flux==52] = 1 tempo_export = tempo.copy() self.tempo = np.ma.masked_where(interv[k]<=0, tempo) self.dict_intermittency_daily[compt] = self.tempo tempo_export[interv[k]<=0] = -9999 tempo_export[mask<=0] = -9999 output_path = self.tifs_file+'/intermittency_weekly_t('+str(compt)+').tif' # if export_tif==True: toolbox.export_tif(self.geographic.watershed_dem, tempo_export, output_path, -9999) compt+=1 inf+=52 sup+=52 np.save(self.save_file+'/intermittency_weekly', self.dict_intermittency_weekly) if intermittency_monthly == True: ### Intermittency monthly logger.info('Exporting monthly intermittency maps') acc_npy_raw = np.load(os.path.join(self.save_file, 'accumulation_flux.npy'), allow_pickle=True).item() acc_npy = list(acc_npy_raw.items())[:] if len(acc_npy_raw)>=12: inf = 0 sup = 12 step = int(round(len(acc_npy_raw)/12)) compt=0 for i in range(step): logger.debug('Processing monthly intermittency t: %d / %d', i, step) interv = list(acc_npy)[inf:sup] for key in range(len(interv)): with rasterio.open(self.geographic.watershed_dem) as src: mask = src.read(1) interv[key] = np.ma.masked_array(interv[key][1], mask=(mask<0)) zero = acc_npy_raw[0] * 0 for j in range(len(interv)): tempo = interv[j].copy() tempo[tempo>0] = 1 zero = zero + tempo days_flux = zero.copy() days_flux = np.ma.masked_array(days_flux, mask=(mask<0)) days_flux = np.ma.masked_array(days_flux, mask=(days_flux<=0)) for k in range(len(interv)): tempo = np.ma.masked_where(interv[k]<=0, interv[k]) tempo[days_flux<12] = 0 tempo[days_flux==12] = 1 tempo_export = tempo.copy() self.tempo = np.ma.masked_where(interv[k]<=0, tempo) self.dict_intermittency_monthly[compt] = self.tempo tempo_export[interv[k]<=0] = -9999 tempo_export[mask<=0] = -9999 output_path = self.tifs_file+'/intermittency_monthly_t('+str(compt)+').tif' toolbox.export_tif(self.geographic.watershed_dem, tempo_export, output_path, -9999) compt+=1 inf+=12 sup+=12 np.save(self.save_file+'/intermittency_monthly', self.dict_intermittency_monthly) if intermittency_yearly == True: ### Intermittency monthly logger.info('Exporting yearly intermittency maps') acc_npy_raw = np.load(os.path.join(self.save_file, 'accumulation_flux.npy'), allow_pickle=True).item() acc_npy = list(acc_npy_raw.items())[:] if len(acc_npy_raw)>=1: inf = 0 sup = 1 step = int(round(len(acc_npy_raw)/1)) compt=0 for i in range(step): logger.debug('Processing yearly intermittency t: %d / %d', i, step) interv = list(acc_npy)[inf:sup] for key in range(len(interv)): with rasterio.open(self.geographic.watershed_dem) as src: mask = src.read(1) interv[key] = np.ma.masked_array(interv[key][1], mask=(mask<0)) zero = acc_npy_raw[0] * 0 for j in range(len(interv)): tempo = interv[j].copy() tempo[tempo>0] = 1 zero = zero + tempo days_flux = zero.copy() days_flux = np.ma.masked_array(days_flux, mask=(mask<0)) days_flux = np.ma.masked_array(days_flux, mask=(days_flux<=0)) for k in range(len(interv)): tempo = np.ma.masked_where(interv[k]<=0, interv[k]) tempo[days_flux<1] = 0 tempo[days_flux==1] = 1 tempo_export = tempo.copy() self.tempo = np.ma.masked_where(interv[k]<=0, tempo) self.dict_intermittency_monthly[compt] = self.tempo tempo_export[interv[k]<=0] = -9999 tempo_export[mask<=0] = -9999 output_path = self.tifs_file+'/intermittency_yearly_t('+str(compt)+').tif' toolbox.export_tif(self.geographic.watershed_dem, tempo_export, output_path, -9999) compt+=1 inf+=12 sup+=12 np.save(self.save_file+'/intermittency_yearly', self.dict_intermittency_monthly)
#%% NOTES