Skip to content

Commit 5317b2a

Browse files
authored
Merge pull request #186 from ec-jrc/feature/initialization
Feature/initialization (includes MCT)
2 parents c9ba06b + 7ed10bd commit 5317b2a

File tree

459 files changed

+112219
-136196
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

459 files changed

+112219
-136196
lines changed

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4.3.1.9998
1+
4.3.1.500

docs/3_step5_model-initialisation/index.md

+372-18
Large diffs are not rendered by default.

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@ tomli>=1.2.1
3838
toolz>=0.10.0
3939
xarray>=0.20.2
4040
zipp>=0.6.0
41+
scipy

src/lisflood/Lisflood_dynamic.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,14 @@ def splitlanduse(array1, array2=None, array3=None):
228228
WaterLevelDyn = -9999
229229
# Set water level dynamic wave to dummy value (needed
230230

231-
if option['InitLisflood'] or option['repAverageDis']:
232-
# self.CumQ += self.ChanQ
231+
if option['InitLisflood']:
232+
if (self.TimeSinceStart > np.round(self.NumDaysSpinUp/self.DtDay)) :
233+
self.CumQ += self.ChanQAvg
234+
self.avgdis = self.CumQ/(self.TimeSinceStart + self.TimeSinceStartPrerunChunkInit[0] - np.round(self.NumDaysSpinUp/self.DtDay))
235+
# to calculate average discharge over the entire simulation
236+
elif option['repAverageDis']:
233237
self.CumQ += self.ChanQAvg
234-
#cmcheck - we should use ChanQAvg here not ChanQ
235-
self.avgdis = self.CumQ/self.TimeSinceStart
236-
# to calculate average discharge over the entire simulation
238+
self.avgdis = self.CumQ/(self.TimeSinceStart)
237239

238240
#self.DischargeM3Out += np.where(self.AtLastPointC ,self.ChanQ * self.DtSec,0)
239241
self.DischargeM3Out += np.where(self.AtLastPointC, self.ChanQAvg * self.DtSec, 0)

src/lisflood/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111
__version__ = version
1212
__authors__ = "Ad de Roo, Emiliano Gelati, Peter Burek, Johan van der Knijff, Niko Wanders"
13-
__date__ = "23/01/2024"
14-
__copyright__ = "Copyright 2019-2024, European Commission - Joint Research Centre"
15-
__maintainer__ = "Stefania Grimaldi, Cinzia Mazzetti, Carlo Russo, Damien Decremer, Corentin Carton De Wiart, Valerio Lorini, Ad de Roo"
13+
__date__ = "27/02/2025"
14+
__copyright__ = "Copyright 2019-2025, European Commission - Joint Research Centre"
15+
__maintainer__ = "Stefania Grimaldi, Cinzia Mazzetti, Jesus Casado Rodriguez, Carlo Russo, Damien Decremer, Corentin Carton De Wiart"
1616
__status__ = "Operation"
1717
__institution__ = "European Commission DG Joint Research Centre (JRC) - E1, D2 Units"

src/lisflood/global_modules/decorators.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def __call__(self, *args, **kwargs):
7878
return_data = self.cache[key]
7979
else:
8080
return_data = self.cache[key]
81-
self.found[self.name] += 1
81+
self.found[self.name] += 1
8282
return return_data
8383

8484
@classmethod

src/lisflood/global_modules/default_options.py

+109-23
Large diffs are not rendered by default.

src/lisflood/global_modules/settings.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"""
1818
from __future__ import (absolute_import, print_function, unicode_literals)
1919

20+
2021
from future.backports import OrderedDict
2122
from future.utils import with_metaclass
2223
from nine import (iteritems, str, range, map, nine)
@@ -42,6 +43,7 @@
4243
from .errors import LisfloodError, LisfloodWarning, LisfloodFileError
4344
from .decorators import cached
4445
from .default_options import default_options
46+
##from .add1 import loadmap # Carlo, not sure....
4547

4648

4749
project_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../..'))
@@ -394,6 +396,12 @@ def __init__(self, settings_file, sys_args=[]):
394396
self.step_start_dt = inttodate(self.step_start_int - 1, ref_date_start, binding=self.binding)
395397
self.step_end_dt = inttodate(self.step_end_int - 1, ref_date_start, binding=self.binding)
396398
self.maskpath = self.binding['MaskMap']
399+
self.NumDaysSpinUp_d = self.binding['NumDaysSpinUp']
400+
self.NumDaysSpinUp_d = int(self.NumDaysSpinUp_d)
401+
DtSec_d1 = self.binding['DtSec']
402+
DtSec_d = int(DtSec_d1)
403+
self.DtDay_d = DtSec_d / 86400.
404+
397405

398406
def build_reportedmaps_dicts(self):
399407
self.report_timeseries = self._report_tss()
@@ -448,7 +456,7 @@ def _check_simulation_dates(self):
448456

449457
int_start, str_start = datetoint(self.binding['StepStart'], self.binding)
450458
int_end, str_end = datetoint(self.binding['StepEnd'], self.binding)
451-
459+
self.numsteps = int_end
452460
# test if start and end > begin
453461
if (int_start < 0) or (int_end < 0) or ((int_end - int_start) < 0):
454462
str_begin = begin.strftime("%d/%m/%Y %H:%M")

src/lisflood/hydrological_modules/groundwater.py

+32-13
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,27 @@ def initial(self):
9595
self.var.LZ = np.where(LZInitValue == -9999, LZSteady, LZInitValue)
9696
# Initialise lower store with steady-state value
9797
# if LZInitValue is set to -9999
98+
99+
if option['InitLisflood']:
100+
self.var.LZInflowCUM = loadmap('LZInflowCUMInit')
101+
self.var.TimeSinceStartPrerunChunkInit = loadmap('TimeSinceStartPrerunChunkInit')
102+
if isinstance(self.var.TimeSinceStartPrerunChunkInit, float):
103+
MAP = []
104+
MAP = maskinfo.in_zero() + self.var.TimeSinceStartPrerunChunkInit
105+
self.var.TimeSinceStartPrerunChunkInit= []
106+
self.var.TimeSinceStartPrerunChunkInit = MAP
107+
# Cumulative lower zone inflow [mm]
108+
# Needed for calculation of average LZ inflow (initialisation)
109+
# Added the option to read LZInflowCUM from settings file: this is needed when the prerun is completed in separate chunks (e.g. 1990-200 and 2000-2010).
110+
else:
111+
self.var.NumDaysSpinUp = 0.0 # this allows to write lzavin in the model run, recommended ONLY when the run is done in 1 chunk
112+
self.var.TimeSinceStartPrerunChunkInit = 0.0 # this allows to write lzavin in the model run, recommended ONLY when the run is done in 1 chunk
113+
if isinstance(self.var.TimeSinceStartPrerunChunkInit, float):
114+
MAP = []
115+
MAP = maskinfo.in_zero() + self.var.TimeSinceStartPrerunChunkInit
116+
self.var.TimeSinceStartPrerunChunkInit= []
117+
self.var.TimeSinceStartPrerunChunkInit = MAP
118+
self.var.LZInflowCUM = maskinfo.in_zero() # this allows to write lzavin in the model run, recommended ONLY when the run is done in 1 chunk
98119

99120
# Water in lower store [mm]
100121
self.var.LZThreshold = loadmap('LZThreshold')
@@ -119,9 +140,6 @@ def initial(self):
119140

120141
self.var.GwLossCUM = maskinfo.in_zero()
121142
# Cumulative groundwater loss [mm]
122-
self.var.LZInflowCUM = maskinfo.in_zero()
123-
# Cumulative lower zone inflow [mm]
124-
# Needed for calculation of average LZ inflow (initialisation)
125143

126144
self.var.GwPercUZLZ = self.var.allocateVariableAllVegetation()
127145
self.var.GwLossLZ = maskinfo.in_zero()
@@ -135,8 +153,6 @@ def dynamic(self):
135153
# outflow from LZ to channel stops when LZ is below its threshold.
136154
# LZ can be below its threshold because of water abstractions
137155
self.var.LZOutflow = np.minimum(self.var.LowerZoneK * self.var.LZ, self.var.LZ - self.var.LZThreshold)
138-
# Total UZ
139-
self.var.UZtotal = self.var.deffraction(self.var.UZ)
140156
# Outflow out of lower zone [mm per model timestep]
141157
self.var.LZOutflow = np.maximum(self.var.LZOutflow, 0)
142158
# No outflow if LZ is below threshold
@@ -163,19 +179,22 @@ def dynamic(self):
163179
self.var.LZ = self.var.LZ - self.var.GwLossLZ
164180
# (ground)water in lower response box [mm]
165181

166-
self.var.LZInflowCUM += (self.var.GwPercUZLZPixel - self.var.GwLossLZ)
167-
# cumulative inflow into lower zone (can be used to improve
168-
self.var.LZInflowCUM = np.maximum(self.var.LZInflowCUM, 0.0)
169-
# LZInflowCUM would become negativ, if LZInit is set to high
170-
# therefore this line is preventing LZInflowCUM getting negativ
171-
172182
self.var.GwLossPixel = self.var.GwLossLZ
173183
# from GROUNDWATER TRANSFER to here
174184
# Compute pixel-average fluxes
175185
self.var.GwLossCUM += self.var.GwLossPixel
176186
self.var.GwLossWB = self.var.GwLossPixel
177187
# Accumulated groundwater loss over simulation period [mm]
178-
self.var.LZAvInflow = (self.var.LZInflowCUM * self.var.InvDtDay) / self.var.TimeSinceStart
179-
# Average inflow into lower zone over executed time steps [mm/day]
188+
189+
if (self.var.TimeSinceStart > np.round(self.var.NumDaysSpinUp/self.var.DtDay)) :
190+
self.var.LZInflowCUM += (self.var.GwPercUZLZPixel - self.var.GwLossLZ)
191+
# cumulative inflow into lower zone (can be used to improve
192+
self.var.LZInflowCUM = np.maximum(self.var.LZInflowCUM, 0.0)
193+
# LZInflowCUM would become negativ, if LZInit is set to high
194+
# therefore this line is preventing LZInflowCUM getting negativ
195+
self.var.LZAvInflow = (self.var.LZInflowCUM * self.var.InvDtDay) / ( self.var.TimeSinceStart + self.var.TimeSinceStartPrerunChunkInit[0] - np.round(self.var.NumDaysSpinUp/self.var.DtDay))
196+
# Average inflow into lower zone over executed time steps [mm/day]
197+
self.var.TimeSinceStartPrerunChunk = self.var.TimeSinceStartPrerunChunkInit + self.var.TimeSinceStart - np.round(self.var.NumDaysSpinUp/self.var.DtDay)
198+
180199

181200
self.var.LZOutflowToChannelPixel = self.var.LZOutflowToChannel

src/lisflood/hydrological_modules/lakes.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,8 @@ def initial(self):
5757
binding = settings.binding
5858
maskinfo = MaskInfo.instance()
5959

60-
if option['simulateLakes'] and not option['InitLisflood']:
61-
62-
LakeSitesC = loadmap('LakeSites')
60+
if option['simulateLakes']:
61+
LakeSitesC = loadmap('LakeSites') # moved here to use the caching feature during calibration
6362
LakeSitesC[LakeSitesC < 1] = 0
6463
LakeSitesC[self.var.IsChannel == 0] = 0
6564
# Get rid of any lakes that are not part of the channel network
@@ -75,7 +74,10 @@ def initial(self):
7574
# rebuild lists of reported files with repsimulateLakes and simulateLakes = False
7675
settings.build_reportedmaps_dicts()
7776
return
78-
# break if no lakes
77+
# break if no lakes
78+
LakeSitePcr = loadmap('LakeSites', pcr=True) # moved here to use the caching feature during calibration
79+
80+
if option['simulateLakes'] and not option['InitLisflood']:
7981

8082
self.var.IsStructureKinematic = np.where(LakeSitesC > 0, np.bool8(1), self.var.IsStructureKinematic)
8183
# Add lake locations to structures map (used to modify LddKinematic
@@ -86,7 +88,6 @@ def initial(self):
8688

8789
# PCRaster part
8890
# -----------------------
89-
LakeSitePcr = loadmap('LakeSites', pcr=True)
9091
LakeSitePcr = pcraster.ifthen((pcraster.defined(LakeSitePcr) & pcraster.boolean(decompress(self.var.IsChannel))), LakeSitePcr)
9192
IsStructureLake = pcraster.boolean(LakeSitePcr)
9293
# additional structure map only for lakes to calculate water balance

src/lisflood/hydrological_modules/miscInitial.py

+1
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ def initial(self):
102102
self.var.DtSecChannel = loadmap('DtSecChannel')
103103
# Sub time step used for kinematic wave channel routing [seconds]
104104
# within the model,the smallest out of DtSecChannel and DtSec is used
105+
self.var.NumDaysSpinUp = loadmap('NumDaysSpinUp')
105106

106107
self.var.MMtoM = 0.001
107108
# Multiplier to convert wate depths in mm to meters

src/lisflood/hydrological_modules/readmeteo.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def __init__(self, readmeteo_variable):
3636
# initialise xarray readers
3737
if option['readNetcdfStack']:
3838
self.forcings = {}
39-
for data in ['PrecipitationMaps', 'TavgMaps', 'ET0Maps', 'E0Maps']:
39+
for data in ['PrecipitationMaps', 'TavgMaps', 'ET0Maps', 'ES0Maps', 'E0Maps']:
4040
self.forcings[data] = xarray_reader(data)
4141

4242
# --------------------------------------------------------------------------
@@ -61,6 +61,7 @@ def dynamic(self):
6161
self.var.Precipitation = self.forcings['PrecipitationMaps'][step] * self.var.DtDay * self.var.PrScaling
6262
self.var.Tavg = self.forcings['TavgMaps'][step]
6363
self.var.ETRef = self.forcings['ET0Maps'][step] * self.var.DtDay * self.var.CalEvaporation
64+
self.var.ESRef = self.forcings['ES0Maps'][step] * self.var.DtDay * self.var.CalEvaporation
6465
self.var.EWRef =self.forcings['E0Maps'][step] * self.var.DtDay * self.var.CalEvaporation
6566

6667
else:
@@ -71,11 +72,11 @@ def dynamic(self):
7172
# average DAILY temperature (even if you are running the model on say an hourly time step) [degrees C]
7273
self.var.ETRef = readmapsparse(binding['ET0Maps'], self.var.currentTimeStep(), self.var.ETRef) * self.var.DtDay * self.var.CalEvaporation
7374
# daily reference evapotranspiration (conversion to [mm] per time step)
75+
self.var.ESRef = readmapsparse(binding['ES0Maps'], self.var.currentTimeStep(), self.var.ESRef) * self.var.DtDay * self.var.CalEvaporation
7476
# potential evaporation rate from a bare soil surface (conversion to [mm] per time step)
7577
self.var.EWRef = readmapsparse(binding['E0Maps'], self.var.currentTimeStep(), self.var.EWRef) * self.var.DtDay * self.var.CalEvaporation
7678
# potential evaporation rate from water surface (conversion to [mm] per time step)
7779

78-
self.var.ESRef = (self.var.EWRef + self.var.ETRef)/2
7980

8081
if option['TemperatureInKelvin']:
8182
self.var.Tavg -= 273.15

src/lisflood/hydrological_modules/reservoir.py

+8-9
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,11 @@ def initial(self):
9090

9191
settings = LisSettings.instance()
9292
option = settings.options
93+
binding = settings.binding
9394
maskinfo = MaskInfo.instance()
94-
if option['simulateReservoirs'] and not option['InitLisflood']:
95-
96-
binding = settings.binding
97-
95+
if option['simulateReservoirs']:
9896
# load reservoir locations and keep only those on the channel network
99-
reservoirs = loadmap('ReservoirSites')
97+
reservoirs = loadmap('ReservoirSites') # moved here to use the caching feature during calibration
10098
reservoirs[(reservoirs < 1) | (self.var.IsChannel == 0)] = 0
10199
self.var.ReservoirSitesC = reservoirs
102100
self.var.ReservoirSitesCC = np.compress(reservoirs > 0, reservoirs)
@@ -110,6 +108,9 @@ def initial(self):
110108
# rebuild lists of reported files with simulateReservoirs and repsimulateReservoirs = False
111109
settings.build_reportedmaps_dicts()
112110
return
111+
ReservoirSitePcr = loadmap('ReservoirSites', pcr=True) # moved here to use the caching feature during calibration
112+
113+
if option['simulateReservoirs'] and not option['InitLisflood']:
113114

114115
# Add reservoir locations to structures map
115116
# (used to modify LddKinematic and to calculate LddStructuresKinematic)
@@ -119,11 +120,9 @@ def initial(self):
119120
self.var.IsStructureChan = np.where(self.var.ReservoirSitesC > 0, np.bool8(1), self.var.IsStructureChan)
120121
# Add reservoir locations to structures map (used to modify LddChan
121122
# and to calculate LddStructuresChan)
122-
123-
ReservoirSitePcr = loadmap('ReservoirSites', pcr=True)
123+
124124
self.var.ReservoirSites = ReservoirSitePcr
125-
ReservoirSitePcr = ifthen((defined(ReservoirSitePcr) & boolean(decompress(self.var.IsChannel))), ReservoirSitePcr)
126-
# Get rid of any reservoirs that are not part of the channel network
125+
# filter out reservoirs that are not part of the channel network
127126
# (following logic of 'old' code the inflow into these reservoirs is
128127
# always zero, so either change this or leave them out!)
129128
ReservoirSitePcr = ifthen((defined(ReservoirSitePcr) & boolean(decompress(self.var.IsChannel))), ReservoirSitePcr)

src/lisflood/hydrological_modules/routing.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ def initial(self):
310310
# Initialise discharge at kinematic wave pixels (note that InvBeta is simply 1/beta, computational efficiency!)
311311

312312
self.var.CumQ = maskinfo.in_zero()
313+
if option['InitLisflood']:
314+
self.var.CumQ = loadmap('CumQInit')
313315
# Initialise sum of discharge to calculate average
314316

315317
# ************************************************************
@@ -399,13 +401,12 @@ def initial(self):
399401
# For pixels in order 1 and beyond, upstream contribution is calculated during the calculation step.
400402
# Initialisation is needed when using Lakes because lakes use average discharge from previous step to calculate the inflow.
401403

402-
if option['simulateLakes'] or option['simulateReservoirs'] and not option['InitLisflood']:
403-
# Initialising average discharge for lakes
404-
PrevDischargeAvg = loadmap('PrevDischargeAvg')
405-
# Outflow (x+dx) Q during previous routing sub-step for full cross-section (average over last routing sub-step)
406-
# Used to calculated average Inflow (x) to reservoirs and lakes
407-
self.var.ChanQAvgDt = np.where(PrevDischargeAvg == -9999, self.var.ChanQAvgDt, PrevDischargeAvg) # np
408-
self.var.ChanQKinAvgDt = np.where(PrevDischargeAvg == -9999, self.var.ChanQKinAvgDt, PrevDischargeAvg) # np
404+
# Initialising average discharge for lakes, reservoirs and transmission loss warm start
405+
PrevDischargeAvg = loadmap('PrevDischargeAvg')
406+
# Outflow (x+dx) Q during previous routing sub-step for full cross-section (average over last routing sub-step)
407+
# Used to calculated average Inflow (x) to reservoirs and lakes
408+
self.var.ChanQAvgDt = np.where(PrevDischargeAvg == -9999, self.var.ChanQAvgDt, PrevDischargeAvg) # np
409+
self.var.ChanQKinAvgDt = np.where(PrevDischargeAvg == -9999, self.var.ChanQKinAvgDt, PrevDischargeAvg) # np
409410

410411
# ************************************************************
411412
# ***** CUMULATIVE OUTPUT VARIABLES *************************
@@ -635,7 +636,11 @@ def dynamic(self, NoRoutingExecuted):
635636
self.polder_module.dynamic_inloop()
636637

637638
self.inflow_module.dynamic_inloop(NoRoutingExecuted)
638-
self.transmission_module.dynamic_inloop()
639+
self.transmission_module.dynamic_inloop(NoRoutingExecuted)
640+
641+
# ************************************************************
642+
# ***** CHANNEL FLOW ROUTING: KINEMATIC WAVE ****************
643+
# ************************************************************
639644

640645
if not(option['dynamicWave']):
641646

@@ -882,6 +887,7 @@ def dynamic(self, NoRoutingExecuted):
882887
StorageStep = StorageStep + self.var.ReservoirStorageM3.copy()
883888
# DisStructureSR = np.where(self.var.IsUpsOfStructureKinematicC, sum1 * self.var.DtRouting, 0)
884889
DisStructureSR = np.where(self.var.IsUpsOfStructureChanC, sum1 * self.var.DtRouting, 0)
890+
DisStructureSR[self.var.AtLastPointC == 1 ] = 0 # this line avoids double-counting when a reservoir is located at the outlet of the cacthment
885891
DischargeM3StructuresR = np.take(np.bincount(self.var.Catchments, weights=DisStructureSR), self.var.Catchments)
886892
DischargeM3StructuresR -= self.var.DischargeM3StructuresIni
887893

@@ -890,6 +896,7 @@ def dynamic(self, NoRoutingExecuted):
890896
StorageStep = StorageStep + self.var.LakeStorageM3Balance.copy()
891897
# DisStructureSR = np.where(self.var.IsUpsOfStructureKinematicC, sum1 * self.var.DtRouting, 0)
892898
DisStructureSR = np.where(self.var.IsUpsOfStructureChanC, sum1 * self.var.DtRouting, 0)
899+
DisStructureSR[self.var.AtLastPointC == 1 ] = 0 # this line avoids double-counting when a reservoir is located at the outlet of the cacthment
893900
DischargeM3StructuresR = np.take(np.bincount(self.var.Catchments, weights=DisStructureSR), self.var.Catchments)
894901
DisLake = maskinfo.in_zero()
895902
np.put(DisLake, self.var.LakeIndex, 0.5 * self.var.LakeInflowCC * self.var.DtRouting)

0 commit comments

Comments
 (0)